feat: fix automatic fixable errors and warnings
continuous-integration/drone/pr Build is failing
Details
continuous-integration/drone/pr Build is failing
Details
This commit is contained in:
parent
453589ba8a
commit
9e9cc2d088
24
src/App.vue
24
src/App.vue
|
@ -1,17 +1,17 @@
|
|||
<template>
|
||||
<ready>
|
||||
<template v-if="authUser">
|
||||
<TheNavigation/>
|
||||
<content-auth/>
|
||||
</template>
|
||||
<content-link-share v-else-if="authLinkShare"/>
|
||||
<no-auth-wrapper v-else>
|
||||
<router-view/>
|
||||
</no-auth-wrapper>
|
||||
<Notification/>
|
||||
<ready>
|
||||
<template v-if="authUser">
|
||||
<TheNavigation />
|
||||
<content-auth />
|
||||
</template>
|
||||
<content-link-share v-else-if="authLinkShare" />
|
||||
<no-auth-wrapper v-else>
|
||||
<router-view />
|
||||
</no-auth-wrapper>
|
||||
<Notification />
|
||||
|
||||
<keyboard-shortcuts v-if="keyboardShortcutsActive"/>
|
||||
</ready>
|
||||
<keyboard-shortcuts v-if="keyboardShortcutsActive" />
|
||||
</ready>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<component
|
||||
:is="componentNodeName"
|
||||
class="base-button"
|
||||
:class="{ 'base-button--type-button': isButton }"
|
||||
v-bind="elementBindings"
|
||||
:disabled="disabled || undefined"
|
||||
ref="button"
|
||||
>
|
||||
<slot/>
|
||||
</component>
|
||||
<component
|
||||
:is="componentNodeName"
|
||||
v-bind="elementBindings"
|
||||
ref="button"
|
||||
class="base-button"
|
||||
:class="{ 'base-button--type-button': isButton }"
|
||||
:disabled="disabled || undefined"
|
||||
>
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -1,112 +1,124 @@
|
|||
<template>
|
||||
<card
|
||||
class="has-no-shadow how-it-works-modal"
|
||||
:title="$t('input.datemathHelp.title')">
|
||||
<p>
|
||||
{{ $t('input.datemathHelp.intro') }}
|
||||
</p>
|
||||
<p>
|
||||
<i18n-t keypath="input.datemathHelp.expression" scope="global">
|
||||
<code>now</code>
|
||||
<code>||</code>
|
||||
</i18n-t>
|
||||
</p>
|
||||
<p>
|
||||
<i18n-t keypath="input.datemathHelp.similar" scope="global">
|
||||
<BaseButton
|
||||
href="https://grafana.com/docs/grafana/latest/dashboards/time-range-controls/"
|
||||
target="_blank">
|
||||
Grafana
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
href="https://www.elastic.co/guide/en/elasticsearch/reference/7.3/common-options.html#date-math"
|
||||
target="_blank">
|
||||
Elasticsearch
|
||||
</BaseButton>
|
||||
</i18n-t>
|
||||
</p>
|
||||
<p>{{ $t('misc.forExample') }}</p>
|
||||
<ul>
|
||||
<li><code>+1d</code>{{ $t('input.datemathHelp.add1Day') }}</li>
|
||||
<li><code>-1d</code>{{ $t('input.datemathHelp.minus1Day') }}</li>
|
||||
<li><code>/d</code>{{ $t('input.datemathHelp.roundDay') }}</li>
|
||||
</ul>
|
||||
<p>{{ $t('input.datemathHelp.supportedUnits') }}</p>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>s</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.seconds') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>m</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.minutes') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>h</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.hours') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>H</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.hours') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>d</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.days') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>w</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.weeks') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>M</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.months') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>y</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.years') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<card
|
||||
class="has-no-shadow how-it-works-modal"
|
||||
:title="$t('input.datemathHelp.title')"
|
||||
>
|
||||
<p>
|
||||
{{ $t('input.datemathHelp.intro') }}
|
||||
</p>
|
||||
<p>
|
||||
<i18n-t
|
||||
keypath="input.datemathHelp.expression"
|
||||
scope="global"
|
||||
>
|
||||
<code>now</code>
|
||||
<code>||</code>
|
||||
</i18n-t>
|
||||
</p>
|
||||
<p>
|
||||
<i18n-t
|
||||
keypath="input.datemathHelp.similar"
|
||||
scope="global"
|
||||
>
|
||||
<BaseButton
|
||||
href="https://grafana.com/docs/grafana/latest/dashboards/time-range-controls/"
|
||||
target="_blank"
|
||||
>
|
||||
Grafana
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
href="https://www.elastic.co/guide/en/elasticsearch/reference/7.3/common-options.html#date-math"
|
||||
target="_blank"
|
||||
>
|
||||
Elasticsearch
|
||||
</BaseButton>
|
||||
</i18n-t>
|
||||
</p>
|
||||
<p>{{ $t('misc.forExample') }}</p>
|
||||
<ul>
|
||||
<li><code>+1d</code>{{ $t('input.datemathHelp.add1Day') }}</li>
|
||||
<li><code>-1d</code>{{ $t('input.datemathHelp.minus1Day') }}</li>
|
||||
<li><code>/d</code>{{ $t('input.datemathHelp.roundDay') }}</li>
|
||||
</ul>
|
||||
<p>{{ $t('input.datemathHelp.supportedUnits') }}</p>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>s</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.seconds') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>m</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.minutes') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>h</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.hours') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>H</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.hours') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>d</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.days') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>w</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.weeks') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>M</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.months') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>y</code></td>
|
||||
<td>{{ $t('input.datemathHelp.units.years') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>{{ $t('input.datemathHelp.someExamples') }}</p>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>now</code></td>
|
||||
<td>{{ $t('input.datemathHelp.examples.now') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>now+24h</code></td>
|
||||
<td>{{ $t('input.datemathHelp.examples.in24h') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>now/d</code></td>
|
||||
<td>{{ $t('input.datemathHelp.examples.today') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>now/w</code></td>
|
||||
<td>{{ $t('input.datemathHelp.examples.beginningOfThisWeek') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>now/w+1w</code></td>
|
||||
<td>{{ $t('input.datemathHelp.examples.endOfThisWeek') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>now+30d</code></td>
|
||||
<td>{{ $t('input.datemathHelp.examples.in30Days') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{{ exampleDate }}||+1M/d</code></td>
|
||||
<td>
|
||||
<i18n-t keypath="input.datemathHelp.examples.datePlusMonth" scope="global">
|
||||
<code>{{ exampleDate }}</code>
|
||||
</i18n-t>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</card>
|
||||
<p>{{ $t('input.datemathHelp.someExamples') }}</p>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>now</code></td>
|
||||
<td>{{ $t('input.datemathHelp.examples.now') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>now+24h</code></td>
|
||||
<td>{{ $t('input.datemathHelp.examples.in24h') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>now/d</code></td>
|
||||
<td>{{ $t('input.datemathHelp.examples.today') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>now/w</code></td>
|
||||
<td>{{ $t('input.datemathHelp.examples.beginningOfThisWeek') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>now/w+1w</code></td>
|
||||
<td>{{ $t('input.datemathHelp.examples.endOfThisWeek') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>now+30d</code></td>
|
||||
<td>{{ $t('input.datemathHelp.examples.in30Days') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{{ exampleDate }}||+1M/d</code></td>
|
||||
<td>
|
||||
<i18n-t
|
||||
keypath="input.datemathHelp.examples.datePlusMonth"
|
||||
scope="global"
|
||||
>
|
||||
<code>{{ exampleDate }}</code>
|
||||
</i18n-t>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,72 +1,102 @@
|
|||
<template>
|
||||
<div class="datepicker-with-range-container">
|
||||
<popup>
|
||||
<template #trigger="{toggle}">
|
||||
<slot name="trigger" :toggle="toggle" :buttonText="buttonText"></slot>
|
||||
</template>
|
||||
<template #content="{isOpen}">
|
||||
<div class="datepicker-with-range" :class="{'is-open': isOpen}">
|
||||
<div class="selections">
|
||||
<BaseButton @click="setDateRange(null)" :class="{'is-active': customRangeActive}">
|
||||
{{ $t('misc.custom') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-for="(value, text) in DATE_RANGES"
|
||||
:key="text"
|
||||
@click="setDateRange(value)"
|
||||
:class="{'is-active': from === value[0] && to === value[1]}">
|
||||
{{ $t(`input.datepickerRange.ranges.${text}`) }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
<div class="flatpickr-container input-group">
|
||||
<label class="label">
|
||||
{{ $t('input.datepickerRange.from') }}
|
||||
<div class="field has-addons">
|
||||
<div class="control is-fullwidth">
|
||||
<input class="input" type="text" v-model="from"/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button icon="calendar" variant="secondary" data-toggle/>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label">
|
||||
{{ $t('input.datepickerRange.to') }}
|
||||
<div class="field has-addons">
|
||||
<div class="control is-fullwidth">
|
||||
<input class="input" type="text" v-model="to"/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button icon="calendar" variant="secondary" data-toggle/>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<flat-pickr
|
||||
:config="flatPickerConfig"
|
||||
v-model="flatpickrRange"
|
||||
/>
|
||||
<div class="datepicker-with-range-container">
|
||||
<popup>
|
||||
<template #trigger="{toggle}">
|
||||
<slot
|
||||
name="trigger"
|
||||
:toggle="toggle"
|
||||
:button-text="buttonText"
|
||||
/>
|
||||
</template>
|
||||
<template #content="{isOpen}">
|
||||
<div
|
||||
class="datepicker-with-range"
|
||||
:class="{'is-open': isOpen}"
|
||||
>
|
||||
<div class="selections">
|
||||
<BaseButton
|
||||
:class="{'is-active': customRangeActive}"
|
||||
@click="setDateRange(null)"
|
||||
>
|
||||
{{ $t('misc.custom') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-for="(value, text) in DATE_RANGES"
|
||||
:key="text"
|
||||
:class="{'is-active': from === value[0] && to === value[1]}"
|
||||
@click="setDateRange(value)"
|
||||
>
|
||||
{{ $t(`input.datepickerRange.ranges.${text}`) }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
<div class="flatpickr-container input-group">
|
||||
<label class="label">
|
||||
{{ $t('input.datepickerRange.from') }}
|
||||
<div class="field has-addons">
|
||||
<div class="control is-fullwidth">
|
||||
<input
|
||||
v-model="from"
|
||||
class="input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
icon="calendar"
|
||||
variant="secondary"
|
||||
data-toggle
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label">
|
||||
{{ $t('input.datepickerRange.to') }}
|
||||
<div class="field has-addons">
|
||||
<div class="control is-fullwidth">
|
||||
<input
|
||||
v-model="to"
|
||||
class="input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
icon="calendar"
|
||||
variant="secondary"
|
||||
data-toggle
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<flat-pickr
|
||||
v-model="flatpickrRange"
|
||||
:config="flatPickerConfig"
|
||||
/>
|
||||
|
||||
<p>
|
||||
{{ $t('input.datemathHelp.canuse') }}
|
||||
<BaseButton class="has-text-primary" @click="showHowItWorks = true">
|
||||
{{ $t('input.datemathHelp.learnhow') }}
|
||||
</BaseButton>
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('input.datemathHelp.canuse') }}
|
||||
<BaseButton
|
||||
class="has-text-primary"
|
||||
@click="showHowItWorks = true"
|
||||
>
|
||||
{{ $t('input.datemathHelp.learnhow') }}
|
||||
</BaseButton>
|
||||
</p>
|
||||
|
||||
<modal
|
||||
@close="() => showHowItWorks = false"
|
||||
:enabled="showHowItWorks"
|
||||
transition-name="fade"
|
||||
:overflow="true"
|
||||
variant="hint-modal"
|
||||
>
|
||||
<DatemathHelp/>
|
||||
</modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</popup>
|
||||
</div>
|
||||
<modal
|
||||
:enabled="showHowItWorks"
|
||||
transition-name="fade"
|
||||
:overflow="true"
|
||||
variant="hint-modal"
|
||||
@close="() => showHowItWorks = false"
|
||||
>
|
||||
<DatemathHelp />
|
||||
</modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -10,7 +10,10 @@ const Logo = computed(() => now.value.getMonth() === 5 ? LogoFullPride : LogoFul
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Logo alt="Vikunja" class="logo" />
|
||||
<Logo
|
||||
alt="Vikunja"
|
||||
class="logo"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<BaseButton
|
||||
class="menu-show-button"
|
||||
@click="baseStore.toggleMenu()"
|
||||
@shortkey="() => baseStore.toggleMenu()"
|
||||
v-shortcut="'Control+e'"
|
||||
:title="$t('keyboardShortcuts.toggleMenu')"
|
||||
:aria-label="menuActive ? $t('misc.hideMenu') : $t('misc.showMenu')"
|
||||
/>
|
||||
<BaseButton
|
||||
v-shortcut="'Control+e'"
|
||||
class="menu-show-button"
|
||||
:title="$t('keyboardShortcuts.toggleMenu')"
|
||||
:aria-label="menuActive ? $t('misc.hideMenu') : $t('misc.showMenu')"
|
||||
@click="baseStore.toggleMenu()"
|
||||
@shortkey="() => baseStore.toggleMenu()"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
<template>
|
||||
<BaseButton class="menu-bottom-link" :href="poweredByUrl" target="_blank">
|
||||
{{ $t('misc.poweredBy') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="menu-bottom-link"
|
||||
:href="poweredByUrl"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t('misc.poweredBy') }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,93 +1,119 @@
|
|||
<template>
|
||||
<header
|
||||
:class="{'has-background': background, 'menu-active': menuActive}"
|
||||
aria-label="main navigation"
|
||||
class="navbar main-theme is-fixed-top d-print-none"
|
||||
>
|
||||
<router-link :to="{name: 'home'}" class="logo-link">
|
||||
<Logo width="164" height="48"/>
|
||||
</router-link>
|
||||
<MenuButton class="menu-button"/>
|
||||
<div class="list-title" ref="listTitle" v-show="currentList.id">
|
||||
<template v-if="currentList.id">
|
||||
<h1
|
||||
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
|
||||
class="title">
|
||||
{{ currentList.title === '' ? $t('misc.loading') : getListTitle(currentList) }}
|
||||
</h1>
|
||||
<header
|
||||
:class="{'has-background': background, 'menu-active': menuActive}"
|
||||
aria-label="main navigation"
|
||||
class="navbar main-theme is-fixed-top d-print-none"
|
||||
>
|
||||
<router-link
|
||||
:to="{name: 'home'}"
|
||||
class="logo-link"
|
||||
>
|
||||
<Logo
|
||||
width="164"
|
||||
height="48"
|
||||
/>
|
||||
</router-link>
|
||||
<MenuButton class="menu-button" />
|
||||
<div
|
||||
v-show="currentList.id"
|
||||
ref="listTitle"
|
||||
class="list-title"
|
||||
>
|
||||
<template v-if="currentList.id">
|
||||
<h1
|
||||
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
|
||||
class="title"
|
||||
>
|
||||
{{ currentList.title === '' ? $t('misc.loading') : getListTitle(currentList) }}
|
||||
</h1>
|
||||
|
||||
<BaseButton :to="{name: 'list.info', params: {listId: currentList.id}}" class="info-button">
|
||||
<icon icon="circle-info"/>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
:to="{name: 'list.info', params: {listId: currentList.id}}"
|
||||
class="info-button"
|
||||
>
|
||||
<icon icon="circle-info" />
|
||||
</BaseButton>
|
||||
|
||||
<list-settings-dropdown v-if="canWriteCurrentList && currentList.id !== -1" :list="currentList"/>
|
||||
</template>
|
||||
</div>
|
||||
<list-settings-dropdown
|
||||
v-if="canWriteCurrentList && currentList.id !== -1"
|
||||
:list="currentList"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
<update/>
|
||||
<BaseButton
|
||||
@click="openQuickActions"
|
||||
class="trigger-button pr-0"
|
||||
v-shortcut="'Control+k'"
|
||||
:title="$t('keyboardShortcuts.quickSearch')"
|
||||
>
|
||||
<icon icon="search"/>
|
||||
</BaseButton>
|
||||
<notifications/>
|
||||
<div class="user">
|
||||
<dropdown class="is-right" ref="usernameDropdown">
|
||||
<template #trigger="{toggleOpen}">
|
||||
<x-button
|
||||
class="username-dropdown-trigger"
|
||||
@click="toggleOpen()"
|
||||
variant="secondary"
|
||||
:shadow="false"
|
||||
>
|
||||
<img :src="userAvatar" alt="" class="avatar" width="40" height="40"/>
|
||||
<span class="username">{{ userInfo.name !== '' ? userInfo.name : userInfo.username }}</span>
|
||||
<span class="icon is-small">
|
||||
<icon icon="chevron-down"/>
|
||||
</span>
|
||||
</x-button>
|
||||
</template>
|
||||
<div class="navbar-end">
|
||||
<update />
|
||||
<BaseButton
|
||||
v-shortcut="'Control+k'"
|
||||
class="trigger-button pr-0"
|
||||
:title="$t('keyboardShortcuts.quickSearch')"
|
||||
@click="openQuickActions"
|
||||
>
|
||||
<icon icon="search" />
|
||||
</BaseButton>
|
||||
<notifications />
|
||||
<div class="user">
|
||||
<dropdown
|
||||
ref="usernameDropdown"
|
||||
class="is-right"
|
||||
>
|
||||
<template #trigger="{toggleOpen}">
|
||||
<x-button
|
||||
class="username-dropdown-trigger"
|
||||
variant="secondary"
|
||||
:shadow="false"
|
||||
@click="toggleOpen()"
|
||||
>
|
||||
<img
|
||||
:src="userAvatar"
|
||||
alt=""
|
||||
class="avatar"
|
||||
width="40"
|
||||
height="40"
|
||||
>
|
||||
<span class="username">{{ userInfo.name !== '' ? userInfo.name : userInfo.username }}</span>
|
||||
<span class="icon is-small">
|
||||
<icon icon="chevron-down" />
|
||||
</span>
|
||||
</x-button>
|
||||
</template>
|
||||
|
||||
<dropdown-item
|
||||
:to="{name: 'user.settings'}"
|
||||
>
|
||||
{{ $t('user.settings.title') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
v-if="imprintUrl"
|
||||
:href="imprintUrl"
|
||||
>
|
||||
{{ $t('navigation.imprint') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
v-if="privacyPolicyUrl"
|
||||
:href="privacyPolicyUrl"
|
||||
>
|
||||
{{ $t('navigation.privacy') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
@click="baseStore.setKeyboardShortcutsActive(true)"
|
||||
>
|
||||
{{ $t('keyboardShortcuts.title') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{name: 'about'}"
|
||||
>
|
||||
{{ $t('about.title') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
@click="logout()"
|
||||
>
|
||||
{{ $t('user.auth.logout') }}
|
||||
</dropdown-item>
|
||||
</dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<dropdown-item
|
||||
:to="{name: 'user.settings'}"
|
||||
>
|
||||
{{ $t('user.settings.title') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
v-if="imprintUrl"
|
||||
:href="imprintUrl"
|
||||
>
|
||||
{{ $t('navigation.imprint') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
v-if="privacyPolicyUrl"
|
||||
:href="privacyPolicyUrl"
|
||||
>
|
||||
{{ $t('navigation.privacy') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
@click="baseStore.setKeyboardShortcutsActive(true)"
|
||||
>
|
||||
{{ $t('keyboardShortcuts.title') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{name: 'about'}"
|
||||
>
|
||||
{{ $t('about.title') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
@click="logout()"
|
||||
>
|
||||
{{ $t('user.auth.logout') }}
|
||||
</dropdown-item>
|
||||
</dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,62 +1,66 @@
|
|||
<template>
|
||||
<div class="content-auth">
|
||||
<BaseButton
|
||||
v-if="menuActive"
|
||||
@click="baseStore.setMenuActive(false)"
|
||||
class="menu-hide-button d-print-none"
|
||||
>
|
||||
<icon icon="times"/>
|
||||
</BaseButton>
|
||||
<div
|
||||
:class="{'has-background': background || blurHash}"
|
||||
:style="{'background-image': blurHash && `url(${blurHash})`}"
|
||||
class="app-container"
|
||||
>
|
||||
<div
|
||||
:class="{'is-visible': background}"
|
||||
class="app-container-background background-fade-in d-print-none"
|
||||
:style="{'background-image': background && `url(${background})`}"></div>
|
||||
<navigation class="d-print-none"/>
|
||||
<main
|
||||
:class="[
|
||||
{ 'is-menu-enabled': menuActive },
|
||||
$route.name,
|
||||
]"
|
||||
class="app-content"
|
||||
>
|
||||
<BaseButton
|
||||
v-if="menuActive"
|
||||
@click="baseStore.setMenuActive(false)"
|
||||
class="mobile-overlay d-print-none"
|
||||
/>
|
||||
<div class="content-auth">
|
||||
<BaseButton
|
||||
v-if="menuActive"
|
||||
class="menu-hide-button d-print-none"
|
||||
@click="baseStore.setMenuActive(false)"
|
||||
>
|
||||
<icon icon="times" />
|
||||
</BaseButton>
|
||||
<div
|
||||
:class="{'has-background': background || blurHash}"
|
||||
:style="{'background-image': blurHash && `url(${blurHash})`}"
|
||||
class="app-container"
|
||||
>
|
||||
<div
|
||||
:class="{'is-visible': background}"
|
||||
class="app-container-background background-fade-in d-print-none"
|
||||
:style="{'background-image': background && `url(${background})`}"
|
||||
/>
|
||||
<navigation class="d-print-none" />
|
||||
<main
|
||||
:class="[
|
||||
{ 'is-menu-enabled': menuActive },
|
||||
$route.name,
|
||||
]"
|
||||
class="app-content"
|
||||
>
|
||||
<BaseButton
|
||||
v-if="menuActive"
|
||||
class="mobile-overlay d-print-none"
|
||||
@click="baseStore.setMenuActive(false)"
|
||||
/>
|
||||
|
||||
<quick-actions/>
|
||||
<quick-actions />
|
||||
|
||||
<router-view :route="routeWithModal" v-slot="{ Component }">
|
||||
<keep-alive :include="['list.list', 'list.gantt', 'list.table', 'list.kanban']">
|
||||
<component :is="Component"/>
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
<router-view
|
||||
v-slot="{ Component }"
|
||||
:route="routeWithModal"
|
||||
>
|
||||
<keep-alive :include="['list.list', 'list.gantt', 'list.table', 'list.kanban']">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
|
||||
<modal
|
||||
v-if="currentModal"
|
||||
@close="closeModal()"
|
||||
variant="scrolling"
|
||||
class="task-detail-view-modal"
|
||||
>
|
||||
<component :is="currentModal"/>
|
||||
</modal>
|
||||
<modal
|
||||
v-if="currentModal"
|
||||
variant="scrolling"
|
||||
class="task-detail-view-modal"
|
||||
@close="closeModal()"
|
||||
>
|
||||
<component :is="currentModal" />
|
||||
</modal>
|
||||
|
||||
<BaseButton
|
||||
class="keyboard-shortcuts-button d-print-none"
|
||||
@click="showKeyboardShortcuts()"
|
||||
v-shortcut="'?'"
|
||||
>
|
||||
<icon icon="keyboard"/>
|
||||
</BaseButton>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<BaseButton
|
||||
v-shortcut="'?'"
|
||||
class="keyboard-shortcuts-button d-print-none"
|
||||
@click="showKeyboardShortcuts()"
|
||||
>
|
||||
<icon icon="keyboard" />
|
||||
</BaseButton>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,25 +1,29 @@
|
|||
<template>
|
||||
<div
|
||||
:class="[background ? 'has-background' : '', $route.name as string +'-view']"
|
||||
:style="{'background-image': `url(${background})`}"
|
||||
class="link-share-container"
|
||||
>
|
||||
<div class="container has-text-centered link-share-view">
|
||||
<div class="column is-10 is-offset-1">
|
||||
<Logo class="logo" v-if="logoVisible"/>
|
||||
<h1
|
||||
:class="{'m-0': !logoVisible}"
|
||||
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
|
||||
class="title">
|
||||
{{ currentList.title === '' ? $t('misc.loading') : currentList.title }}
|
||||
</h1>
|
||||
<div class="box has-text-left view">
|
||||
<router-view/>
|
||||
<PoweredByLink/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
:class="[background ? 'has-background' : '', $route.name as string +'-view']"
|
||||
:style="{'background-image': `url(${background})`}"
|
||||
class="link-share-container"
|
||||
>
|
||||
<div class="container has-text-centered link-share-view">
|
||||
<div class="column is-10 is-offset-1">
|
||||
<Logo
|
||||
v-if="logoVisible"
|
||||
class="logo"
|
||||
/>
|
||||
<h1
|
||||
:class="{'m-0': !logoVisible}"
|
||||
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
|
||||
class="title"
|
||||
>
|
||||
{{ currentList.title === '' ? $t('misc.loading') : currentList.title }}
|
||||
</h1>
|
||||
<div class="box has-text-left view">
|
||||
<router-view />
|
||||
<PoweredByLink />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,142 +1,187 @@
|
|||
<template>
|
||||
<aside :class="{'is-active': menuActive}" class="namespace-container">
|
||||
<nav class="menu top-menu">
|
||||
<router-link :to="{name: 'home'}" class="logo">
|
||||
<Logo width="164" height="48"/>
|
||||
</router-link>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<router-link :to="{ name: 'home'}" v-shortcut="'g o'">
|
||||
<span class="icon">
|
||||
<icon icon="calendar"/>
|
||||
</span>
|
||||
{{ $t('navigation.overview') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'tasks.range'}" v-shortcut="'g u'">
|
||||
<span class="icon">
|
||||
<icon :icon="['far', 'calendar-alt']"/>
|
||||
</span>
|
||||
{{ $t('navigation.upcoming') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'namespaces.index'}" v-shortcut="'g n'">
|
||||
<span class="icon">
|
||||
<icon icon="layer-group"/>
|
||||
</span>
|
||||
{{ $t('namespace.title') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'labels.index'}" v-shortcut="'g a'">
|
||||
<span class="icon">
|
||||
<icon icon="tags"/>
|
||||
</span>
|
||||
{{ $t('label.title') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'teams.index'}" v-shortcut="'g m'">
|
||||
<span class="icon">
|
||||
<icon icon="users"/>
|
||||
</span>
|
||||
{{ $t('team.title') }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<aside
|
||||
:class="{'is-active': menuActive}"
|
||||
class="namespace-container"
|
||||
>
|
||||
<nav class="menu top-menu">
|
||||
<router-link
|
||||
:to="{name: 'home'}"
|
||||
class="logo"
|
||||
>
|
||||
<Logo
|
||||
width="164"
|
||||
height="48"
|
||||
/>
|
||||
</router-link>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<router-link
|
||||
v-shortcut="'g o'"
|
||||
:to="{ name: 'home'}"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon icon="calendar" />
|
||||
</span>
|
||||
{{ $t('navigation.overview') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link
|
||||
v-shortcut="'g u'"
|
||||
:to="{ name: 'tasks.range'}"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon :icon="['far', 'calendar-alt']" />
|
||||
</span>
|
||||
{{ $t('navigation.upcoming') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link
|
||||
v-shortcut="'g n'"
|
||||
:to="{ name: 'namespaces.index'}"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon icon="layer-group" />
|
||||
</span>
|
||||
{{ $t('namespace.title') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link
|
||||
v-shortcut="'g a'"
|
||||
:to="{ name: 'labels.index'}"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon icon="tags" />
|
||||
</span>
|
||||
{{ $t('label.title') }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link
|
||||
v-shortcut="'g m'"
|
||||
:to="{ name: 'teams.index'}"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon icon="users" />
|
||||
</span>
|
||||
{{ $t('team.title') }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<nav class="menu namespaces-lists loader-container is-loading-small" :class="{'is-loading': loading}">
|
||||
<template v-for="(n, nk) in namespaces" :key="n.id">
|
||||
<div class="namespace-title" :class="{'has-menu': n.id > 0}">
|
||||
<BaseButton
|
||||
@click="toggleLists(n.id)"
|
||||
class="menu-label"
|
||||
v-tooltip="namespaceTitles[nk]"
|
||||
>
|
||||
<ColorBubble
|
||||
v-if="n.hexColor !== ''"
|
||||
:color="n.hexColor"
|
||||
class="mr-1"
|
||||
/>
|
||||
<span class="name">{{ namespaceTitles[nk] }}</span>
|
||||
<div
|
||||
class="icon is-small toggle-lists-icon pl-2"
|
||||
:class="{'active': typeof listsVisible[n.id] !== 'undefined' ? listsVisible[n.id] : true}"
|
||||
>
|
||||
<icon icon="chevron-down"/>
|
||||
</div>
|
||||
<span class="count" :class="{'ml-2 mr-0': n.id > 0}">
|
||||
({{ namespaceListsCount[nk] }})
|
||||
</span>
|
||||
</BaseButton>
|
||||
<namespace-settings-dropdown :namespace="n" v-if="n.id > 0"/>
|
||||
</div>
|
||||
<!--
|
||||
<nav
|
||||
class="menu namespaces-lists loader-container is-loading-small"
|
||||
:class="{'is-loading': loading}"
|
||||
>
|
||||
<template
|
||||
v-for="(n, nk) in namespaces"
|
||||
:key="n.id"
|
||||
>
|
||||
<div
|
||||
class="namespace-title"
|
||||
:class="{'has-menu': n.id > 0}"
|
||||
>
|
||||
<BaseButton
|
||||
v-tooltip="namespaceTitles[nk]"
|
||||
class="menu-label"
|
||||
@click="toggleLists(n.id)"
|
||||
>
|
||||
<ColorBubble
|
||||
v-if="n.hexColor !== ''"
|
||||
:color="n.hexColor"
|
||||
class="mr-1"
|
||||
/>
|
||||
<span class="name">{{ namespaceTitles[nk] }}</span>
|
||||
<div
|
||||
class="icon is-small toggle-lists-icon pl-2"
|
||||
:class="{'active': typeof listsVisible[n.id] !== 'undefined' ? listsVisible[n.id] : true}"
|
||||
>
|
||||
<icon icon="chevron-down" />
|
||||
</div>
|
||||
<span
|
||||
class="count"
|
||||
:class="{'ml-2 mr-0': n.id > 0}"
|
||||
>
|
||||
({{ namespaceListsCount[nk] }})
|
||||
</span>
|
||||
</BaseButton>
|
||||
<namespace-settings-dropdown
|
||||
v-if="n.id > 0"
|
||||
:namespace="n"
|
||||
/>
|
||||
</div>
|
||||
<!--
|
||||
NOTE: a v-model / computed setter is not possible, since the updateActiveLists function
|
||||
triggered by the change needs to have access to the current namespace
|
||||
-->
|
||||
<draggable
|
||||
v-if="listsVisible[n.id] ?? true"
|
||||
v-bind="dragOptions"
|
||||
:modelValue="activeLists[nk]"
|
||||
@update:modelValue="(lists) => updateActiveLists(n, lists)"
|
||||
group="namespace-lists"
|
||||
@start="() => drag = true"
|
||||
@end="saveListPosition"
|
||||
handle=".handle"
|
||||
:disabled="n.id < 0 || undefined"
|
||||
tag="ul"
|
||||
item-key="id"
|
||||
:data-namespace-id="n.id"
|
||||
:data-namespace-index="nk"
|
||||
:component-data="{
|
||||
type: 'transition-group',
|
||||
name: !drag ? 'flip-list' : null,
|
||||
class: [
|
||||
'menu-list can-be-hidden',
|
||||
{ 'dragging-disabled': n.id < 0 }
|
||||
]
|
||||
}"
|
||||
>
|
||||
<template #item="{element: l}">
|
||||
<li
|
||||
class="list-menu loader-container is-loading-small"
|
||||
:class="{'is-loading': listUpdating[l.id]}"
|
||||
>
|
||||
<BaseButton
|
||||
:to="{ name: 'list.index', params: { listId: l.id} }"
|
||||
class="list-menu-link"
|
||||
:class="{'router-link-exact-active': currentList.id === l.id}"
|
||||
>
|
||||
<span class="icon handle">
|
||||
<icon icon="grip-lines"/>
|
||||
</span>
|
||||
<ColorBubble
|
||||
v-if="l.hexColor !== ''"
|
||||
:color="l.hexColor"
|
||||
class="mr-1"
|
||||
/>
|
||||
<span class="list-menu-title">{{ getListTitle(l) }}</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="favorite"
|
||||
:class="{'is-favorite': l.isFavorite}"
|
||||
@click="listStore.toggleListFavorite(l)"
|
||||
>
|
||||
<icon :icon="l.isFavorite ? 'star' : ['far', 'star']"/>
|
||||
</BaseButton>
|
||||
<list-settings-dropdown :list="l" v-if="l.id > 0"/>
|
||||
<span class="list-setting-spacer" v-else></span>
|
||||
</li>
|
||||
</template>
|
||||
</draggable>
|
||||
</template>
|
||||
</nav>
|
||||
<PoweredByLink/>
|
||||
</aside>
|
||||
<draggable
|
||||
v-if="listsVisible[n.id] ?? true"
|
||||
v-bind="dragOptions"
|
||||
:model-value="activeLists[nk]"
|
||||
group="namespace-lists"
|
||||
handle=".handle"
|
||||
:disabled="n.id < 0 || undefined"
|
||||
tag="ul"
|
||||
item-key="id"
|
||||
:data-namespace-id="n.id"
|
||||
:data-namespace-index="nk"
|
||||
:component-data="{
|
||||
type: 'transition-group',
|
||||
name: !drag ? 'flip-list' : null,
|
||||
class: [
|
||||
'menu-list can-be-hidden',
|
||||
{ 'dragging-disabled': n.id < 0 }
|
||||
]
|
||||
}"
|
||||
@update:modelValue="(lists) => updateActiveLists(n, lists)"
|
||||
@start="() => drag = true"
|
||||
@end="saveListPosition"
|
||||
>
|
||||
<template #item="{element: l}">
|
||||
<li
|
||||
class="list-menu loader-container is-loading-small"
|
||||
:class="{'is-loading': listUpdating[l.id]}"
|
||||
>
|
||||
<BaseButton
|
||||
:to="{ name: 'list.index', params: { listId: l.id} }"
|
||||
class="list-menu-link"
|
||||
:class="{'router-link-exact-active': currentList.id === l.id}"
|
||||
>
|
||||
<span class="icon handle">
|
||||
<icon icon="grip-lines" />
|
||||
</span>
|
||||
<ColorBubble
|
||||
v-if="l.hexColor !== ''"
|
||||
:color="l.hexColor"
|
||||
class="mr-1"
|
||||
/>
|
||||
<span class="list-menu-title">{{ getListTitle(l) }}</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="favorite"
|
||||
:class="{'is-favorite': l.isFavorite}"
|
||||
@click="listStore.toggleListFavorite(l)"
|
||||
>
|
||||
<icon :icon="l.isFavorite ? 'star' : ['far', 'star']" />
|
||||
</BaseButton>
|
||||
<list-settings-dropdown
|
||||
v-if="l.id > 0"
|
||||
:list="l"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="list-setting-spacer"
|
||||
/>
|
||||
</li>
|
||||
</template>
|
||||
</draggable>
|
||||
</template>
|
||||
</nav>
|
||||
<PoweredByLink />
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
<template>
|
||||
<div class="update-notification" v-if="updateAvailable">
|
||||
<p>{{ $t('update.available') }}</p>
|
||||
<x-button @click="refreshApp()" :shadow="false" class="has-no-text-wrap">
|
||||
{{ $t('update.do') }}
|
||||
</x-button>
|
||||
</div>
|
||||
<div
|
||||
v-if="updateAvailable"
|
||||
class="update-notification"
|
||||
>
|
||||
<p>{{ $t('update.available') }}</p>
|
||||
<x-button
|
||||
:shadow="false"
|
||||
class="has-no-text-wrap"
|
||||
@click="refreshApp()"
|
||||
>
|
||||
{{ $t('update.do') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,31 +1,34 @@
|
|||
<template>
|
||||
<BaseButton
|
||||
class="button"
|
||||
:class="[
|
||||
variantClass,
|
||||
{
|
||||
'is-loading': loading,
|
||||
'has-no-shadow': !shadow || variant === 'tertiary',
|
||||
}
|
||||
]"
|
||||
>
|
||||
<icon
|
||||
v-if="showIconOnly"
|
||||
:icon="icon"
|
||||
:style="{'color': iconColor !== '' ? iconColor : false}"
|
||||
/>
|
||||
<span class="icon is-small" v-else-if="icon !== ''">
|
||||
<icon
|
||||
:icon="icon"
|
||||
:style="{'color': iconColor !== '' ? iconColor : false}"
|
||||
/>
|
||||
</span>
|
||||
<slot />
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="button"
|
||||
:class="[
|
||||
variantClass,
|
||||
{
|
||||
'is-loading': loading,
|
||||
'has-no-shadow': !shadow || variant === 'tertiary',
|
||||
}
|
||||
]"
|
||||
>
|
||||
<icon
|
||||
v-if="showIconOnly"
|
||||
:icon="icon"
|
||||
:style="{'color': iconColor !== '' ? iconColor : false}"
|
||||
/>
|
||||
<span
|
||||
v-else-if="icon !== ''"
|
||||
class="icon is-small"
|
||||
>
|
||||
<icon
|
||||
:icon="icon"
|
||||
:style="{'color': iconColor !== '' ? iconColor : false}"
|
||||
/>
|
||||
</span>
|
||||
<slot />
|
||||
</BaseButton>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default { name: 'x-button' }
|
||||
export default { name: 'XButton' }
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,37 +1,68 @@
|
|||
<template>
|
||||
<div class="color-picker-container">
|
||||
<datalist :id="colorListID">
|
||||
<option v-for="defaultColor in defaultColors" :key="defaultColor" :value="defaultColor" />
|
||||
</datalist>
|
||||
<div class="color-picker-container">
|
||||
<datalist :id="colorListID">
|
||||
<option
|
||||
v-for="defaultColor in defaultColors"
|
||||
:key="defaultColor"
|
||||
:value="defaultColor"
|
||||
/>
|
||||
</datalist>
|
||||
|
||||
<div class="picker">
|
||||
<input
|
||||
class="picker__input"
|
||||
type="color"
|
||||
v-model="color"
|
||||
:list="colorListID"
|
||||
:class="{'is-empty': isEmpty}"
|
||||
/>
|
||||
<svg class="picker__pattern" v-show="isEmpty" viewBox="0 0 22 22" fill="fff">
|
||||
<pattern id="checker" width="11" height="11" patternUnits="userSpaceOnUse" fill="FFF">
|
||||
<rect fill="#cccccc" x="0" width="5.5" height="5.5" y="0"></rect>
|
||||
<rect fill="#cccccc" x="5.5" width="5.5" height="5.5" y="5.5"></rect>
|
||||
</pattern>
|
||||
<rect width="22" height="22" fill="url(#checker)"></rect>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="picker">
|
||||
<input
|
||||
v-model="color"
|
||||
class="picker__input"
|
||||
type="color"
|
||||
:list="colorListID"
|
||||
:class="{'is-empty': isEmpty}"
|
||||
>
|
||||
<svg
|
||||
v-show="isEmpty"
|
||||
class="picker__pattern"
|
||||
viewBox="0 0 22 22"
|
||||
fill="fff"
|
||||
>
|
||||
<pattern
|
||||
id="checker"
|
||||
width="11"
|
||||
height="11"
|
||||
patternUnits="userSpaceOnUse"
|
||||
fill="FFF"
|
||||
>
|
||||
<rect
|
||||
fill="#cccccc"
|
||||
x="0"
|
||||
width="5.5"
|
||||
height="5.5"
|
||||
y="0"
|
||||
/>
|
||||
<rect
|
||||
fill="#cccccc"
|
||||
x="5.5"
|
||||
width="5.5"
|
||||
height="5.5"
|
||||
y="5.5"
|
||||
/>
|
||||
</pattern>
|
||||
<rect
|
||||
width="22"
|
||||
height="22"
|
||||
fill="url(#checker)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<x-button
|
||||
v-if="!isEmpty"
|
||||
:disabled="isEmpty"
|
||||
@click="reset"
|
||||
class="is-small ml-2"
|
||||
:shadow="false"
|
||||
variant="secondary"
|
||||
>
|
||||
{{ $t('input.resetColor') }}
|
||||
</x-button>
|
||||
</div>
|
||||
<x-button
|
||||
v-if="!isEmpty"
|
||||
:disabled="isEmpty"
|
||||
class="is-small ml-2"
|
||||
:shadow="false"
|
||||
variant="secondary"
|
||||
@click="reset"
|
||||
>
|
||||
{{ $t('input.resetColor') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,91 +1,98 @@
|
|||
<template>
|
||||
<div class="datepicker">
|
||||
<BaseButton @click.stop="toggleDatePopup" class="show" :disabled="disabled || undefined">
|
||||
{{ date === null ? chooseDateLabel : formatDateShort(date) }}
|
||||
</BaseButton>
|
||||
<div class="datepicker">
|
||||
<BaseButton
|
||||
class="show"
|
||||
:disabled="disabled || undefined"
|
||||
@click.stop="toggleDatePopup"
|
||||
>
|
||||
{{ date === null ? chooseDateLabel : formatDateShort(date) }}
|
||||
</BaseButton>
|
||||
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="datepicker-popup" ref="datepickerPopup">
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="show"
|
||||
ref="datepickerPopup"
|
||||
class="datepicker-popup"
|
||||
>
|
||||
<BaseButton
|
||||
v-if="(new Date()).getHours() < 21"
|
||||
class="datepicker__quick-select-date"
|
||||
@click.stop="setDate('today')"
|
||||
>
|
||||
<span class="icon"><icon :icon="['far', 'calendar-alt']" /></span>
|
||||
<span class="text">
|
||||
<span>{{ $t('input.datepicker.today') }}</span>
|
||||
<span class="weekday">{{ getWeekdayFromStringInterval('today') }}</span>
|
||||
</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="datepicker__quick-select-date"
|
||||
@click.stop="setDate('tomorrow')"
|
||||
>
|
||||
<span class="icon"><icon :icon="['far', 'sun']" /></span>
|
||||
<span class="text">
|
||||
<span>{{ $t('input.datepicker.tomorrow') }}</span>
|
||||
<span class="weekday">{{ getWeekdayFromStringInterval('tomorrow') }}</span>
|
||||
</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="datepicker__quick-select-date"
|
||||
@click.stop="setDate('nextMonday')"
|
||||
>
|
||||
<span class="icon"><icon icon="coffee" /></span>
|
||||
<span class="text">
|
||||
<span>{{ $t('input.datepicker.nextMonday') }}</span>
|
||||
<span class="weekday">{{ getWeekdayFromStringInterval('nextMonday') }}</span>
|
||||
</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="datepicker__quick-select-date"
|
||||
@click.stop="setDate('thisWeekend')"
|
||||
>
|
||||
<span class="icon"><icon icon="cocktail" /></span>
|
||||
<span class="text">
|
||||
<span>{{ $t('input.datepicker.thisWeekend') }}</span>
|
||||
<span class="weekday">{{ getWeekdayFromStringInterval('thisWeekend') }}</span>
|
||||
</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="datepicker__quick-select-date"
|
||||
@click.stop="setDate('laterThisWeek')"
|
||||
>
|
||||
<span class="icon"><icon icon="chess-knight" /></span>
|
||||
<span class="text">
|
||||
<span>{{ $t('input.datepicker.laterThisWeek') }}</span>
|
||||
<span class="weekday">{{ getWeekdayFromStringInterval('laterThisWeek') }}</span>
|
||||
</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="datepicker__quick-select-date"
|
||||
@click.stop="setDate('nextWeek')"
|
||||
>
|
||||
<span class="icon"><icon icon="forward" /></span>
|
||||
<span class="text">
|
||||
<span>{{ $t('input.datepicker.nextWeek') }}</span>
|
||||
<span class="weekday">{{ getWeekdayFromStringInterval('nextWeek') }}</span>
|
||||
</span>
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton
|
||||
v-if="(new Date()).getHours() < 21"
|
||||
class="datepicker__quick-select-date"
|
||||
@click.stop="setDate('today')"
|
||||
>
|
||||
<span class="icon"><icon :icon="['far', 'calendar-alt']"/></span>
|
||||
<span class="text">
|
||||
<span>{{ $t('input.datepicker.today') }}</span>
|
||||
<span class="weekday">{{ getWeekdayFromStringInterval('today') }}</span>
|
||||
</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="datepicker__quick-select-date"
|
||||
@click.stop="setDate('tomorrow')"
|
||||
>
|
||||
<span class="icon"><icon :icon="['far', 'sun']"/></span>
|
||||
<span class="text">
|
||||
<span>{{ $t('input.datepicker.tomorrow') }}</span>
|
||||
<span class="weekday">{{ getWeekdayFromStringInterval('tomorrow') }}</span>
|
||||
</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="datepicker__quick-select-date"
|
||||
@click.stop="setDate('nextMonday')"
|
||||
>
|
||||
<span class="icon"><icon icon="coffee"/></span>
|
||||
<span class="text">
|
||||
<span>{{ $t('input.datepicker.nextMonday') }}</span>
|
||||
<span class="weekday">{{ getWeekdayFromStringInterval('nextMonday') }}</span>
|
||||
</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="datepicker__quick-select-date"
|
||||
@click.stop="setDate('thisWeekend')"
|
||||
>
|
||||
<span class="icon"><icon icon="cocktail"/></span>
|
||||
<span class="text">
|
||||
<span>{{ $t('input.datepicker.thisWeekend') }}</span>
|
||||
<span class="weekday">{{ getWeekdayFromStringInterval('thisWeekend') }}</span>
|
||||
</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="datepicker__quick-select-date"
|
||||
@click.stop="setDate('laterThisWeek')"
|
||||
>
|
||||
<span class="icon"><icon icon="chess-knight"/></span>
|
||||
<span class="text">
|
||||
<span>{{ $t('input.datepicker.laterThisWeek') }}</span>
|
||||
<span class="weekday">{{ getWeekdayFromStringInterval('laterThisWeek') }}</span>
|
||||
</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="datepicker__quick-select-date"
|
||||
@click.stop="setDate('nextWeek')"
|
||||
>
|
||||
<span class="icon"><icon icon="forward"/></span>
|
||||
<span class="text">
|
||||
<span>{{ $t('input.datepicker.nextWeek') }}</span>
|
||||
<span class="weekday">{{ getWeekdayFromStringInterval('nextWeek') }}</span>
|
||||
</span>
|
||||
</BaseButton>
|
||||
<flat-pickr
|
||||
v-model="flatPickrDate"
|
||||
:config="flatPickerConfig"
|
||||
class="input"
|
||||
/>
|
||||
|
||||
<flat-pickr
|
||||
:config="flatPickerConfig"
|
||||
class="input"
|
||||
v-model="flatPickrDate"
|
||||
/>
|
||||
|
||||
<x-button
|
||||
class="datepicker__close-button"
|
||||
:shadow="false"
|
||||
@click="close"
|
||||
v-cy="'closeDatepicker'"
|
||||
>
|
||||
{{ $t('misc.confirm') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<x-button
|
||||
v-cy="'closeDatepicker'"
|
||||
class="datepicker__close-button"
|
||||
:shadow="false"
|
||||
@click="close"
|
||||
>
|
||||
{{ $t('misc.confirm') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,69 +1,92 @@
|
|||
<template>
|
||||
<div class="editor">
|
||||
<div class="clear"></div>
|
||||
<div class="editor">
|
||||
<div class="clear" />
|
||||
|
||||
<vue-easymde
|
||||
:configs="config"
|
||||
@change="bubble"
|
||||
@update:modelValue="handleInput"
|
||||
class="content"
|
||||
v-if="isEditActive"
|
||||
v-model="text"/>
|
||||
<vue-easymde
|
||||
v-if="isEditActive"
|
||||
v-model="text"
|
||||
:configs="config"
|
||||
class="content"
|
||||
@change="bubble"
|
||||
@update:modelValue="handleInput"
|
||||
/>
|
||||
|
||||
<div class="preview content" v-html="preview" v-if="isPreviewActive && text !== ''">
|
||||
</div>
|
||||
<div
|
||||
v-if="isPreviewActive && text !== ''"
|
||||
class="preview content"
|
||||
v-html="preview"
|
||||
/>
|
||||
|
||||
<p class="has-text-centered has-text-grey is-italic my-5" v-if="showPreviewText">
|
||||
{{ emptyText }}
|
||||
<template v-if="isEditEnabled">
|
||||
<ButtonLink
|
||||
@click="toggleEdit"
|
||||
v-shortcut="editShortcut"
|
||||
class="d-print-none">
|
||||
{{ $t('input.editor.edit') }}
|
||||
</ButtonLink>.
|
||||
</template>
|
||||
</p>
|
||||
<p
|
||||
v-if="showPreviewText"
|
||||
class="has-text-centered has-text-grey is-italic my-5"
|
||||
>
|
||||
{{ emptyText }}
|
||||
<template v-if="isEditEnabled">
|
||||
<ButtonLink
|
||||
v-shortcut="editShortcut"
|
||||
class="d-print-none"
|
||||
@click="toggleEdit"
|
||||
>
|
||||
{{ $t('input.editor.edit') }}
|
||||
</ButtonLink>.
|
||||
</template>
|
||||
</p>
|
||||
|
||||
<ul class="actions d-print-none" v-if="bottomActions.length > 0">
|
||||
<li v-if="isEditEnabled && !showPreviewText && showSave">
|
||||
<BaseButton
|
||||
v-if="showEditButton"
|
||||
@click="toggleEdit"
|
||||
v-shortcut="editShortcut">
|
||||
{{ $t('input.editor.edit') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-else-if="isEditActive"
|
||||
@click="toggleEdit"
|
||||
class="done-edit">
|
||||
{{ $t('misc.save') }}
|
||||
</BaseButton>
|
||||
</li>
|
||||
<li v-for="(action, k) in bottomActions" :key="k">
|
||||
<BaseButton @click="action.action">{{ action.title }}</BaseButton>
|
||||
</li>
|
||||
</ul>
|
||||
<template v-else-if="isEditEnabled && showSave">
|
||||
<ul v-if="showEditButton" class="actions d-print-none">
|
||||
<li>
|
||||
<BaseButton
|
||||
@click="toggleEdit"
|
||||
v-shortcut="editShortcut">
|
||||
{{ $t('input.editor.edit') }}
|
||||
</BaseButton>
|
||||
</li>
|
||||
</ul>
|
||||
<x-button
|
||||
v-else-if="isEditActive"
|
||||
@click="toggleEdit"
|
||||
variant="secondary"
|
||||
:shadow="false"
|
||||
v-cy="'saveEditor'">
|
||||
{{ $t('misc.save') }}
|
||||
</x-button>
|
||||
</template>
|
||||
</div>
|
||||
<ul
|
||||
v-if="bottomActions.length > 0"
|
||||
class="actions d-print-none"
|
||||
>
|
||||
<li v-if="isEditEnabled && !showPreviewText && showSave">
|
||||
<BaseButton
|
||||
v-if="showEditButton"
|
||||
v-shortcut="editShortcut"
|
||||
@click="toggleEdit"
|
||||
>
|
||||
{{ $t('input.editor.edit') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-else-if="isEditActive"
|
||||
class="done-edit"
|
||||
@click="toggleEdit"
|
||||
>
|
||||
{{ $t('misc.save') }}
|
||||
</BaseButton>
|
||||
</li>
|
||||
<li
|
||||
v-for="(action, k) in bottomActions"
|
||||
:key="k"
|
||||
>
|
||||
<BaseButton @click="action.action">
|
||||
{{ action.title }}
|
||||
</BaseButton>
|
||||
</li>
|
||||
</ul>
|
||||
<template v-else-if="isEditEnabled && showSave">
|
||||
<ul
|
||||
v-if="showEditButton"
|
||||
class="actions d-print-none"
|
||||
>
|
||||
<li>
|
||||
<BaseButton
|
||||
v-shortcut="editShortcut"
|
||||
@click="toggleEdit"
|
||||
>
|
||||
{{ $t('input.editor.edit') }}
|
||||
</BaseButton>
|
||||
</li>
|
||||
</ul>
|
||||
<x-button
|
||||
v-else-if="isEditActive"
|
||||
v-cy="'saveEditor'"
|
||||
variant="secondary"
|
||||
:shadow="false"
|
||||
@click="toggleEdit"
|
||||
>
|
||||
{{ $t('misc.save') }}
|
||||
</x-button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -85,7 +108,7 @@ import BaseButton from '@/components/base/BaseButton.vue'
|
|||
import ButtonLink from '@/components/misc/ButtonLink.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'editor',
|
||||
name: 'Editor',
|
||||
components: {
|
||||
VueEasymde,
|
||||
BaseButton,
|
||||
|
@ -137,14 +160,6 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
computed: {
|
||||
showPreviewText() {
|
||||
return this.isPreviewActive && this.text === '' && this.emptyText !== ''
|
||||
},
|
||||
showEditButton() {
|
||||
return !this.isEditActive && this.text !== ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
text: '',
|
||||
|
@ -163,6 +178,14 @@ export default defineComponent({
|
|||
checkboxId: createRandomID(),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showPreviewText() {
|
||||
return this.isPreviewActive && this.text === '' && this.emptyText !== ''
|
||||
},
|
||||
showEditButton() {
|
||||
return !this.isEditActive && this.text !== ''
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue(modelValue) {
|
||||
this.text = modelValue
|
||||
|
|
|
@ -1,22 +1,34 @@
|
|||
<template>
|
||||
<div :class="{'is-disabled': disabled}" class="fancycheckbox">
|
||||
<input
|
||||
:checked="checked"
|
||||
:disabled="disabled || undefined"
|
||||
:id="checkBoxId"
|
||||
@change="(event) => updateData(event.target.checked)"
|
||||
type="checkbox"/>
|
||||
<label :for="checkBoxId" class="check">
|
||||
<svg height="18px" viewBox="0 0 18 18" width="18px">
|
||||
<path
|
||||
d="M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z"></path>
|
||||
<polyline points="1 9 7 14 15 4"></polyline>
|
||||
</svg>
|
||||
<span>
|
||||
<slot></slot>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
:class="{'is-disabled': disabled}"
|
||||
class="fancycheckbox"
|
||||
>
|
||||
<input
|
||||
:id="checkBoxId"
|
||||
:checked="checked"
|
||||
:disabled="disabled || undefined"
|
||||
type="checkbox"
|
||||
@change="(event) => updateData(event.target.checked)"
|
||||
>
|
||||
<label
|
||||
:for="checkBoxId"
|
||||
class="check"
|
||||
>
|
||||
<svg
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
width="18px"
|
||||
>
|
||||
<path
|
||||
d="M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z"
|
||||
/>
|
||||
<polyline points="1 9 7 14 15 4" />
|
||||
</svg>
|
||||
<span>
|
||||
<slot />
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -25,13 +37,7 @@ import {defineComponent} from 'vue'
|
|||
import {createRandomID} from '@/helpers/randomId'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'fancycheckbox',
|
||||
data() {
|
||||
return {
|
||||
checked: false,
|
||||
checkBoxId: `fancycheckbox_${createRandomID()}`,
|
||||
}
|
||||
},
|
||||
name: 'Fancycheckbox',
|
||||
props: {
|
||||
modelValue: {
|
||||
required: false,
|
||||
|
@ -43,6 +49,12 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
data() {
|
||||
return {
|
||||
checked: false,
|
||||
checkBoxId: `fancycheckbox_${createRandomID()}`,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
handler(modelValue) {
|
||||
|
|
|
@ -1,85 +1,107 @@
|
|||
<template>
|
||||
<div
|
||||
class="multiselect"
|
||||
:class="{'has-search-results': searchResultsVisible}"
|
||||
ref="multiselectRoot"
|
||||
tabindex="-1"
|
||||
@focus="focus"
|
||||
>
|
||||
<div class="control" :class="{'is-loading': loading || localLoading}">
|
||||
<div
|
||||
class="input-wrapper input"
|
||||
:class="{'has-multiple': hasMultiple}">
|
||||
<template v-if="Array.isArray(internalValue)">
|
||||
<template v-for="(item, key) in internalValue">
|
||||
<slot name="tag" :item="item">
|
||||
<span :key="`item${key}`" class="tag ml-2 mt-2">
|
||||
{{ label !== '' ? item[label] : item }}
|
||||
<BaseButton @click="() => remove(item)" class="delete is-small"></BaseButton>
|
||||
</span>
|
||||
</slot>
|
||||
</template>
|
||||
</template>
|
||||
<div
|
||||
ref="multiselectRoot"
|
||||
class="multiselect"
|
||||
:class="{'has-search-results': searchResultsVisible}"
|
||||
tabindex="-1"
|
||||
@focus="focus"
|
||||
>
|
||||
<div
|
||||
class="control"
|
||||
:class="{'is-loading': loading || localLoading}"
|
||||
>
|
||||
<div
|
||||
class="input-wrapper input"
|
||||
:class="{'has-multiple': hasMultiple}"
|
||||
>
|
||||
<template v-if="Array.isArray(internalValue)">
|
||||
<template v-for="(item, key) in internalValue">
|
||||
<slot
|
||||
name="tag"
|
||||
:item="item"
|
||||
>
|
||||
<span
|
||||
:key="`item${key}`"
|
||||
class="tag ml-2 mt-2"
|
||||
>
|
||||
{{ label !== '' ? item[label] : item }}
|
||||
<BaseButton
|
||||
class="delete is-small"
|
||||
@click="() => remove(item)"
|
||||
/>
|
||||
</span>
|
||||
</slot>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
v-model="query"
|
||||
@keyup="search"
|
||||
@keyup.enter.exact.prevent="() => createOrSelectOnEnter()"
|
||||
:placeholder="placeholder"
|
||||
@keydown.down.exact.prevent="() => preSelect(0)"
|
||||
ref="searchInput"
|
||||
@focus="handleFocus"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
ref="searchInput"
|
||||
v-model="query"
|
||||
type="text"
|
||||
class="input"
|
||||
:placeholder="placeholder"
|
||||
@keyup="search"
|
||||
@keyup.enter.exact.prevent="() => createOrSelectOnEnter()"
|
||||
@keydown.down.exact.prevent="() => preSelect(0)"
|
||||
@focus="handleFocus"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<transition name="fade">
|
||||
<div class="search-results" :class="{'search-results-inline': inline}" v-if="searchResultsVisible">
|
||||
<BaseButton
|
||||
class="is-fullwidth"
|
||||
v-for="(data, index) in filteredSearchResults"
|
||||
:key="index"
|
||||
:ref="(el) => setResult(el, index)"
|
||||
@keydown.up.prevent="() => preSelect(index - 1)"
|
||||
@keydown.down.prevent="() => preSelect(index + 1)"
|
||||
@click.prevent.stop="() => select(data)"
|
||||
>
|
||||
<span>
|
||||
<slot name="searchResult" :option="data">
|
||||
<span class="search-result">{{ label !== '' ? data[label] : data }}</span>
|
||||
</slot>
|
||||
</span>
|
||||
<span class="hint-text">
|
||||
{{ selectPlaceholder }}
|
||||
</span>
|
||||
</BaseButton>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="searchResultsVisible"
|
||||
class="search-results"
|
||||
:class="{'search-results-inline': inline}"
|
||||
>
|
||||
<BaseButton
|
||||
v-for="(data, index) in filteredSearchResults"
|
||||
:key="index"
|
||||
:ref="(el) => setResult(el, index)"
|
||||
class="is-fullwidth"
|
||||
@keydown.up.prevent="() => preSelect(index - 1)"
|
||||
@keydown.down.prevent="() => preSelect(index + 1)"
|
||||
@click.prevent.stop="() => select(data)"
|
||||
>
|
||||
<span>
|
||||
<slot
|
||||
name="searchResult"
|
||||
:option="data"
|
||||
>
|
||||
<span class="search-result">{{ label !== '' ? data[label] : data }}</span>
|
||||
</slot>
|
||||
</span>
|
||||
<span class="hint-text">
|
||||
{{ selectPlaceholder }}
|
||||
</span>
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton
|
||||
v-if="creatableAvailable"
|
||||
class="is-fullwidth"
|
||||
:ref="(el) => setResult(el, filteredSearchResults.length)"
|
||||
@keydown.up.prevent="() => preSelect(filteredSearchResults.length - 1)"
|
||||
@keydown.down.prevent="() => preSelect(filteredSearchResults.length + 1)"
|
||||
@keyup.enter.prevent="create"
|
||||
@click.prevent.stop="create"
|
||||
>
|
||||
<span>
|
||||
<slot name="searchResult" :option="query">
|
||||
<span class="search-result">
|
||||
{{ query }}
|
||||
</span>
|
||||
</slot>
|
||||
</span>
|
||||
<span class="hint-text">
|
||||
{{ createPlaceholder }}
|
||||
</span>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
</div>
|
||||
<BaseButton
|
||||
v-if="creatableAvailable"
|
||||
:ref="(el) => setResult(el, filteredSearchResults.length)"
|
||||
class="is-fullwidth"
|
||||
@keydown.up.prevent="() => preSelect(filteredSearchResults.length - 1)"
|
||||
@keydown.down.prevent="() => preSelect(filteredSearchResults.length + 1)"
|
||||
@keyup.enter.prevent="create"
|
||||
@click.prevent.stop="create"
|
||||
>
|
||||
<span>
|
||||
<slot
|
||||
name="searchResult"
|
||||
:option="query"
|
||||
>
|
||||
<span class="search-result">
|
||||
{{ query }}
|
||||
</span>
|
||||
</slot>
|
||||
</span>
|
||||
<span class="hint-text">
|
||||
{{ createPlaceholder }}
|
||||
</span>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,29 +1,33 @@
|
|||
<template>
|
||||
<div class="password-field">
|
||||
<input
|
||||
class="input"
|
||||
id="password"
|
||||
name="password"
|
||||
:placeholder="$t('user.auth.passwordPlaceholder')"
|
||||
required
|
||||
:type="passwordFieldType"
|
||||
autocomplete="current-password"
|
||||
@keyup.enter="e => $emit('submit', e)"
|
||||
:tabindex="props.tabindex"
|
||||
@focusout="validate"
|
||||
@input="handleInput"
|
||||
/>
|
||||
<BaseButton
|
||||
@click="togglePasswordFieldType"
|
||||
class="password-field-type-toggle"
|
||||
:aria-label="passwordFieldType === 'password' ? $t('user.auth.showPassword') : $t('user.auth.hidePassword')"
|
||||
v-tooltip="passwordFieldType === 'password' ? $t('user.auth.showPassword') : $t('user.auth.hidePassword')">
|
||||
<icon :icon="passwordFieldType === 'password' ? 'eye' : 'eye-slash'"/>
|
||||
</BaseButton>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="!isValid">
|
||||
{{ $t('user.auth.passwordRequired') }}
|
||||
</p>
|
||||
<div class="password-field">
|
||||
<input
|
||||
id="password"
|
||||
class="input"
|
||||
name="password"
|
||||
:placeholder="$t('user.auth.passwordPlaceholder')"
|
||||
required
|
||||
:type="passwordFieldType"
|
||||
autocomplete="current-password"
|
||||
:tabindex="props.tabindex"
|
||||
@keyup.enter="e => $emit('submit', e)"
|
||||
@focusout="validate"
|
||||
@input="handleInput"
|
||||
>
|
||||
<BaseButton
|
||||
v-tooltip="passwordFieldType === 'password' ? $t('user.auth.showPassword') : $t('user.auth.hidePassword')"
|
||||
class="password-field-type-toggle"
|
||||
:aria-label="passwordFieldType === 'password' ? $t('user.auth.showPassword') : $t('user.auth.hidePassword')"
|
||||
@click="togglePasswordFieldType"
|
||||
>
|
||||
<icon :icon="passwordFieldType === 'password' ? 'eye' : 'eye-slash'" />
|
||||
</BaseButton>
|
||||
</div>
|
||||
<p
|
||||
v-if="!isValid"
|
||||
class="help is-danger"
|
||||
>
|
||||
{{ $t('user.auth.passwordRequired') }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<div class="vue-easymde" ref="easymdeRef">
|
||||
<div
|
||||
ref="easymdeRef"
|
||||
class="vue-easymde"
|
||||
>
|
||||
<textarea
|
||||
class="vue-simplemde-textarea"
|
||||
:name="name"
|
||||
|
|
|
@ -1,78 +1,78 @@
|
|||
<template>
|
||||
<dropdown>
|
||||
<template v-if="isSavedFilter(list)">
|
||||
<dropdown-item
|
||||
:to="{ name: 'filter.settings.edit', params: { listId: list.id } }"
|
||||
icon="pen"
|
||||
>
|
||||
{{ $t('menu.edit') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'filter.settings.delete', params: { listId: list.id } }"
|
||||
icon="trash-alt"
|
||||
>
|
||||
{{ $t('misc.delete') }}
|
||||
</dropdown-item>
|
||||
</template>
|
||||
<dropdown>
|
||||
<template v-if="isSavedFilter(list)">
|
||||
<dropdown-item
|
||||
:to="{ name: 'filter.settings.edit', params: { listId: list.id } }"
|
||||
icon="pen"
|
||||
>
|
||||
{{ $t('menu.edit') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'filter.settings.delete', params: { listId: list.id } }"
|
||||
icon="trash-alt"
|
||||
>
|
||||
{{ $t('misc.delete') }}
|
||||
</dropdown-item>
|
||||
</template>
|
||||
|
||||
<template v-else-if="list.isArchived">
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.archive', params: { listId: list.id } }"
|
||||
icon="archive"
|
||||
>
|
||||
{{ $t('menu.unarchive') }}
|
||||
</dropdown-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.edit', params: { listId: list.id } }"
|
||||
icon="pen"
|
||||
>
|
||||
{{ $t('menu.edit') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
v-if="backgroundsEnabled"
|
||||
:to="{ name: 'list.settings.background', params: { listId: list.id } }"
|
||||
icon="image"
|
||||
>
|
||||
{{ $t('menu.setBackground') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.share', params: { listId: list.id } }"
|
||||
icon="share-alt"
|
||||
>
|
||||
{{ $t('menu.share') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.duplicate', params: { listId: list.id } }"
|
||||
icon="paste"
|
||||
>
|
||||
{{ $t('menu.duplicate') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.archive', params: { listId: list.id } }"
|
||||
icon="archive"
|
||||
>
|
||||
{{ $t('menu.archive') }}
|
||||
</dropdown-item>
|
||||
<task-subscription
|
||||
class="has-no-shadow"
|
||||
:is-button="false"
|
||||
entity="list"
|
||||
:entity-id="list.id"
|
||||
:model-value="list.subscription"
|
||||
@update:model-value="sub => subscription = sub"
|
||||
type="dropdown"
|
||||
/>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.delete', params: { listId: list.id } }"
|
||||
icon="trash-alt"
|
||||
class="has-text-danger"
|
||||
>
|
||||
{{ $t('menu.delete') }}
|
||||
</dropdown-item>
|
||||
</template>
|
||||
</dropdown>
|
||||
<template v-else-if="list.isArchived">
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.archive', params: { listId: list.id } }"
|
||||
icon="archive"
|
||||
>
|
||||
{{ $t('menu.unarchive') }}
|
||||
</dropdown-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.edit', params: { listId: list.id } }"
|
||||
icon="pen"
|
||||
>
|
||||
{{ $t('menu.edit') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
v-if="backgroundsEnabled"
|
||||
:to="{ name: 'list.settings.background', params: { listId: list.id } }"
|
||||
icon="image"
|
||||
>
|
||||
{{ $t('menu.setBackground') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.share', params: { listId: list.id } }"
|
||||
icon="share-alt"
|
||||
>
|
||||
{{ $t('menu.share') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.duplicate', params: { listId: list.id } }"
|
||||
icon="paste"
|
||||
>
|
||||
{{ $t('menu.duplicate') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.archive', params: { listId: list.id } }"
|
||||
icon="archive"
|
||||
>
|
||||
{{ $t('menu.archive') }}
|
||||
</dropdown-item>
|
||||
<task-subscription
|
||||
class="has-no-shadow"
|
||||
:is-button="false"
|
||||
entity="list"
|
||||
:entity-id="list.id"
|
||||
:model-value="list.subscription"
|
||||
type="dropdown"
|
||||
@update:model-value="sub => subscription = sub"
|
||||
/>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.delete', params: { listId: list.id } }"
|
||||
icon="trash-alt"
|
||||
class="has-text-danger"
|
||||
>
|
||||
{{ $t('menu.delete') }}
|
||||
</dropdown-item>
|
||||
</template>
|
||||
</dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
<template>
|
||||
<x-button
|
||||
v-if="hasFilters"
|
||||
variant="secondary"
|
||||
@click="clearFilters"
|
||||
>
|
||||
{{ $t('filters.clear') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
@click="() => modalOpen = true"
|
||||
variant="secondary"
|
||||
icon="filter"
|
||||
>
|
||||
{{ $t('filters.title') }}
|
||||
</x-button>
|
||||
<modal
|
||||
@close="() => modalOpen = false"
|
||||
:enabled="modalOpen"
|
||||
transition-name="fade"
|
||||
:overflow="true"
|
||||
variant="hint-modal"
|
||||
>
|
||||
<filters
|
||||
:has-title="true"
|
||||
v-model="value"
|
||||
ref="filters"
|
||||
class="filter-popup"
|
||||
/>
|
||||
</modal>
|
||||
<x-button
|
||||
v-if="hasFilters"
|
||||
variant="secondary"
|
||||
@click="clearFilters"
|
||||
>
|
||||
{{ $t('filters.clear') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
icon="filter"
|
||||
@click="() => modalOpen = true"
|
||||
>
|
||||
{{ $t('filters.title') }}
|
||||
</x-button>
|
||||
<modal
|
||||
:enabled="modalOpen"
|
||||
transition-name="fade"
|
||||
:overflow="true"
|
||||
variant="hint-modal"
|
||||
@close="() => modalOpen = false"
|
||||
>
|
||||
<filters
|
||||
ref="filters"
|
||||
v-model="value"
|
||||
:has-title="true"
|
||||
class="filter-popup"
|
||||
/>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,194 +1,224 @@
|
|||
<template>
|
||||
<card class="filters has-overflow" :title="hasTitle ? $t('filters.title') : ''">
|
||||
<div class="field is-flex is-flex-direction-column">
|
||||
<fancycheckbox
|
||||
v-model="params.filter_include_nulls"
|
||||
@update:model-value="change()"
|
||||
>
|
||||
{{ $t('filters.attributes.includeNulls') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox
|
||||
v-model="filters.requireAllFilters"
|
||||
@update:model-value="setFilterConcat()"
|
||||
>
|
||||
{{ $t('filters.attributes.requireAll') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="filters.done" @update:model-value="setDoneFilter">
|
||||
{{ $t('filters.attributes.showDoneTasks') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox
|
||||
v-if="!$route.name.includes('list.kanban') || !$route.name.includes('list.table')"
|
||||
v-model="sortAlphabetically"
|
||||
@update:model-value="change()"
|
||||
>
|
||||
{{ $t('filters.attributes.sortAlphabetically') }}
|
||||
</fancycheckbox>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('misc.search') }}</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
:placeholder="$t('misc.search')"
|
||||
v-model="params.s"
|
||||
@blur="change()"
|
||||
@keyup.enter="change()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.priority') }}</label>
|
||||
<div class="control single-value-control">
|
||||
<priority-select
|
||||
:disabled="!filters.usePriority || undefined"
|
||||
v-model.number="filters.priority"
|
||||
@update:model-value="setPriority"
|
||||
/>
|
||||
<fancycheckbox
|
||||
v-model="filters.usePriority"
|
||||
@update:model-value="setPriority"
|
||||
>
|
||||
{{ $t('filters.attributes.enablePriority') }}
|
||||
</fancycheckbox>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.percentDone') }}</label>
|
||||
<div class="control single-value-control">
|
||||
<percent-done-select
|
||||
v-model.number="filters.percentDone"
|
||||
@update:model-value="setPercentDoneFilter"
|
||||
:disabled="!filters.usePercentDone || undefined"
|
||||
/>
|
||||
<fancycheckbox
|
||||
v-model="filters.usePercentDone"
|
||||
@update:model-value="setPercentDoneFilter"
|
||||
>
|
||||
{{ $t('filters.attributes.enablePercentDone') }}
|
||||
</fancycheckbox>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.dueDate') }}</label>
|
||||
<div class="control">
|
||||
<datepicker-with-range
|
||||
v-model="filters.dueDate"
|
||||
@update:model-value="values => setDateFilter('due_date', values)"
|
||||
>
|
||||
<template #trigger="{toggle, buttonText}">
|
||||
<x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2">
|
||||
{{ buttonText }}
|
||||
</x-button>
|
||||
</template>
|
||||
</datepicker-with-range>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.startDate') }}</label>
|
||||
<div class="control">
|
||||
<datepicker-with-range
|
||||
v-model="filters.startDate"
|
||||
@update:model-value="values => setDateFilter('start_date', values)"
|
||||
>
|
||||
<template #trigger="{toggle, buttonText}">
|
||||
<x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2">
|
||||
{{ buttonText }}
|
||||
</x-button>
|
||||
</template>
|
||||
</datepicker-with-range>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.endDate') }}</label>
|
||||
<div class="control">
|
||||
<datepicker-with-range
|
||||
v-model="filters.endDate"
|
||||
@update:model-value="values => setDateFilter('end_date', values)"
|
||||
>
|
||||
<template #trigger="{toggle, buttonText}">
|
||||
<x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2">
|
||||
{{ buttonText }}
|
||||
</x-button>
|
||||
</template>
|
||||
</datepicker-with-range>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.reminders') }}</label>
|
||||
<div class="control">
|
||||
<datepicker-with-range
|
||||
v-model="filters.reminders"
|
||||
@update:model-value="values => setDateFilter('reminders', values)"
|
||||
>
|
||||
<template #trigger="{toggle, buttonText}">
|
||||
<x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2">
|
||||
{{ buttonText }}
|
||||
</x-button>
|
||||
</template>
|
||||
</datepicker-with-range>
|
||||
</div>
|
||||
</div>
|
||||
<card
|
||||
class="filters has-overflow"
|
||||
:title="hasTitle ? $t('filters.title') : ''"
|
||||
>
|
||||
<div class="field is-flex is-flex-direction-column">
|
||||
<fancycheckbox
|
||||
v-model="params.filter_include_nulls"
|
||||
@update:model-value="change()"
|
||||
>
|
||||
{{ $t('filters.attributes.includeNulls') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox
|
||||
v-model="filters.requireAllFilters"
|
||||
@update:model-value="setFilterConcat()"
|
||||
>
|
||||
{{ $t('filters.attributes.requireAll') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox
|
||||
v-model="filters.done"
|
||||
@update:model-value="setDoneFilter"
|
||||
>
|
||||
{{ $t('filters.attributes.showDoneTasks') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox
|
||||
v-if="!$route.name.includes('list.kanban') || !$route.name.includes('list.table')"
|
||||
v-model="sortAlphabetically"
|
||||
@update:model-value="change()"
|
||||
>
|
||||
{{ $t('filters.attributes.sortAlphabetically') }}
|
||||
</fancycheckbox>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('misc.search') }}</label>
|
||||
<div class="control">
|
||||
<input
|
||||
v-model="params.s"
|
||||
class="input"
|
||||
:placeholder="$t('misc.search')"
|
||||
@blur="change()"
|
||||
@keyup.enter="change()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.priority') }}</label>
|
||||
<div class="control single-value-control">
|
||||
<priority-select
|
||||
v-model.number="filters.priority"
|
||||
:disabled="!filters.usePriority || undefined"
|
||||
@update:model-value="setPriority"
|
||||
/>
|
||||
<fancycheckbox
|
||||
v-model="filters.usePriority"
|
||||
@update:model-value="setPriority"
|
||||
>
|
||||
{{ $t('filters.attributes.enablePriority') }}
|
||||
</fancycheckbox>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.percentDone') }}</label>
|
||||
<div class="control single-value-control">
|
||||
<percent-done-select
|
||||
v-model.number="filters.percentDone"
|
||||
:disabled="!filters.usePercentDone || undefined"
|
||||
@update:model-value="setPercentDoneFilter"
|
||||
/>
|
||||
<fancycheckbox
|
||||
v-model="filters.usePercentDone"
|
||||
@update:model-value="setPercentDoneFilter"
|
||||
>
|
||||
{{ $t('filters.attributes.enablePercentDone') }}
|
||||
</fancycheckbox>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.dueDate') }}</label>
|
||||
<div class="control">
|
||||
<datepicker-with-range
|
||||
v-model="filters.dueDate"
|
||||
@update:model-value="values => setDateFilter('due_date', values)"
|
||||
>
|
||||
<template #trigger="{toggle, buttonText}">
|
||||
<x-button
|
||||
variant="secondary"
|
||||
:shadow="false"
|
||||
class="mb-2"
|
||||
@click.prevent.stop="toggle()"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</x-button>
|
||||
</template>
|
||||
</datepicker-with-range>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.startDate') }}</label>
|
||||
<div class="control">
|
||||
<datepicker-with-range
|
||||
v-model="filters.startDate"
|
||||
@update:model-value="values => setDateFilter('start_date', values)"
|
||||
>
|
||||
<template #trigger="{toggle, buttonText}">
|
||||
<x-button
|
||||
variant="secondary"
|
||||
:shadow="false"
|
||||
class="mb-2"
|
||||
@click.prevent.stop="toggle()"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</x-button>
|
||||
</template>
|
||||
</datepicker-with-range>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.endDate') }}</label>
|
||||
<div class="control">
|
||||
<datepicker-with-range
|
||||
v-model="filters.endDate"
|
||||
@update:model-value="values => setDateFilter('end_date', values)"
|
||||
>
|
||||
<template #trigger="{toggle, buttonText}">
|
||||
<x-button
|
||||
variant="secondary"
|
||||
:shadow="false"
|
||||
class="mb-2"
|
||||
@click.prevent.stop="toggle()"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</x-button>
|
||||
</template>
|
||||
</datepicker-with-range>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.reminders') }}</label>
|
||||
<div class="control">
|
||||
<datepicker-with-range
|
||||
v-model="filters.reminders"
|
||||
@update:model-value="values => setDateFilter('reminders', values)"
|
||||
>
|
||||
<template #trigger="{toggle, buttonText}">
|
||||
<x-button
|
||||
variant="secondary"
|
||||
:shadow="false"
|
||||
class="mb-2"
|
||||
@click.prevent.stop="toggle()"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</x-button>
|
||||
</template>
|
||||
</datepicker-with-range>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.assignees') }}</label>
|
||||
<div class="control">
|
||||
<multiselect
|
||||
:loading="usersService.loading"
|
||||
:placeholder="$t('team.edit.search')"
|
||||
@search="query => find('users', query)"
|
||||
:search-results="foundusers"
|
||||
@select="() => add('users', 'assignees')"
|
||||
label="username"
|
||||
:multiple="true"
|
||||
@remove="() => remove('users', 'assignees')"
|
||||
v-model="users"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.assignees') }}</label>
|
||||
<div class="control">
|
||||
<multiselect
|
||||
v-model="users"
|
||||
:loading="usersService.loading"
|
||||
:placeholder="$t('team.edit.search')"
|
||||
:search-results="foundusers"
|
||||
label="username"
|
||||
:multiple="true"
|
||||
@search="query => find('users', query)"
|
||||
@select="() => add('users', 'assignees')"
|
||||
@remove="() => remove('users', 'assignees')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.labels') }}</label>
|
||||
<div class="control labels-list">
|
||||
<edit-labels v-model="labels" @update:model-value="changeLabelFilter"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.labels') }}</label>
|
||||
<div class="control labels-list">
|
||||
<edit-labels
|
||||
v-model="labels"
|
||||
@update:model-value="changeLabelFilter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template
|
||||
v-if="$route.name === 'filters.create' || $route.name === 'list.edit' || $route.name === 'filter.settings.edit'">
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('list.lists') }}</label>
|
||||
<div class="control">
|
||||
<multiselect
|
||||
:loading="listsService.loading"
|
||||
:placeholder="$t('list.search')"
|
||||
@search="query => find('lists', query)"
|
||||
:search-results="foundlists"
|
||||
@select="() => add('lists', 'list_id')"
|
||||
label="title"
|
||||
@remove="() => remove('lists', 'list_id')"
|
||||
:multiple="true"
|
||||
v-model="lists"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('namespace.namespaces') }}</label>
|
||||
<div class="control">
|
||||
<multiselect
|
||||
:loading="namespaceService.loading"
|
||||
:placeholder="$t('namespace.search')"
|
||||
@search="query => find('namespace', query)"
|
||||
:search-results="foundnamespace"
|
||||
@select="() => add('namespace', 'namespace')"
|
||||
label="title"
|
||||
@remove="() => remove('namespace', 'namespace')"
|
||||
:multiple="true"
|
||||
v-model="namespace"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</card>
|
||||
<template
|
||||
v-if="$route.name === 'filters.create' || $route.name === 'list.edit' || $route.name === 'filter.settings.edit'"
|
||||
>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('list.lists') }}</label>
|
||||
<div class="control">
|
||||
<multiselect
|
||||
v-model="lists"
|
||||
:loading="listsService.loading"
|
||||
:placeholder="$t('list.search')"
|
||||
:search-results="foundlists"
|
||||
label="title"
|
||||
:multiple="true"
|
||||
@search="query => find('lists', query)"
|
||||
@select="() => add('lists', 'list_id')"
|
||||
@remove="() => remove('lists', 'list_id')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('namespace.namespaces') }}</label>
|
||||
<div class="control">
|
||||
<multiselect
|
||||
v-model="namespace"
|
||||
:loading="namespaceService.loading"
|
||||
:placeholder="$t('namespace.search')"
|
||||
:search-results="foundnamespace"
|
||||
label="title"
|
||||
:multiple="true"
|
||||
@search="query => find('namespace', query)"
|
||||
@select="() => add('namespace', 'namespace')"
|
||||
@remove="() => remove('namespace', 'namespace')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -246,7 +276,7 @@ const DEFAULT_FILTERS = {
|
|||
export const ALPHABETICAL_SORT = 'title'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'filters',
|
||||
name: 'Filters',
|
||||
components: {
|
||||
DatepickerWithRange,
|
||||
EditLabels,
|
||||
|
@ -255,6 +285,16 @@ export default defineComponent({
|
|||
PercentDoneSelect,
|
||||
Multiselect,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
required: true,
|
||||
},
|
||||
hasTitle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
data() {
|
||||
return {
|
||||
params: DEFAULT_PARAMS,
|
||||
|
@ -276,30 +316,6 @@ export default defineComponent({
|
|||
namespace: [],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.filters.requireAllFilters = this.params.filter_concat === 'and'
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
required: true,
|
||||
},
|
||||
hasTitle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
watch: {
|
||||
modelValue: {
|
||||
handler(value) {
|
||||
// FIXME: filters should only be converted to snake case in
|
||||
// the last moment
|
||||
this.params = objectToSnakeCase(value)
|
||||
this.prepareFilters()
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
sortAlphabetically: {
|
||||
get() {
|
||||
|
@ -319,6 +335,20 @@ export default defineComponent({
|
|||
return labelStore.filterLabelsByQuery(this.labels, this.labelQuery)
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
handler(value) {
|
||||
// FIXME: filters should only be converted to snake case in
|
||||
// the last moment
|
||||
this.params = objectToSnakeCase(value)
|
||||
this.prepareFilters()
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.filters.requireAllFilters = this.params.filter_concat === 'and'
|
||||
},
|
||||
methods: {
|
||||
change() {
|
||||
const params = {...this.params}
|
||||
|
|
|
@ -1,38 +1,43 @@
|
|||
<template>
|
||||
<router-link
|
||||
:class="{
|
||||
'has-light-text': !colorIsDark(list.hexColor) || background !== null,
|
||||
'has-background': blurHashUrl !== '' || background !== null,
|
||||
}"
|
||||
:style="{
|
||||
'background-color': list.hexColor,
|
||||
'background-image': blurHashUrl !== null ? `url(${blurHashUrl})` : false,
|
||||
}"
|
||||
:to="{ name: 'list.index', params: { listId: list.id} }"
|
||||
class="list-card"
|
||||
v-if="list !== null && (showArchived ? true : !list.isArchived)"
|
||||
>
|
||||
<div
|
||||
class="list-background background-fade-in"
|
||||
:class="{'is-visible': background}"
|
||||
:style="{'background-image': background !== null ? `url(${background})` : undefined}"
|
||||
/>
|
||||
<div class="list-content">
|
||||
<span class="is-archived" v-if="list.isArchived">
|
||||
{{ $t('namespace.archived') }}
|
||||
</span>
|
||||
<BaseButton
|
||||
v-else
|
||||
:class="{'is-favorite': list.isFavorite}"
|
||||
@click.stop="listStore.toggleListFavorite(list)"
|
||||
class="favorite"
|
||||
>
|
||||
<icon :icon="list.isFavorite ? 'star' : ['far', 'star']"/>
|
||||
</BaseButton>
|
||||
<router-link
|
||||
v-if="list !== null && (showArchived ? true : !list.isArchived)"
|
||||
:class="{
|
||||
'has-light-text': !colorIsDark(list.hexColor) || background !== null,
|
||||
'has-background': blurHashUrl !== '' || background !== null,
|
||||
}"
|
||||
:style="{
|
||||
'background-color': list.hexColor,
|
||||
'background-image': blurHashUrl !== null ? `url(${blurHashUrl})` : false,
|
||||
}"
|
||||
:to="{ name: 'list.index', params: { listId: list.id} }"
|
||||
class="list-card"
|
||||
>
|
||||
<div
|
||||
class="list-background background-fade-in"
|
||||
:class="{'is-visible': background}"
|
||||
:style="{'background-image': background !== null ? `url(${background})` : undefined}"
|
||||
/>
|
||||
<div class="list-content">
|
||||
<span
|
||||
v-if="list.isArchived"
|
||||
class="is-archived"
|
||||
>
|
||||
{{ $t('namespace.archived') }}
|
||||
</span>
|
||||
<BaseButton
|
||||
v-else
|
||||
:class="{'is-favorite': list.isFavorite}"
|
||||
class="favorite"
|
||||
@click.stop="listStore.toggleListFavorite(list)"
|
||||
>
|
||||
<icon :icon="list.isFavorite ? 'star' : ['far', 'star']" />
|
||||
</BaseButton>
|
||||
|
||||
<div class="title">{{ list.title }}</div>
|
||||
</div>
|
||||
</router-link>
|
||||
<div class="title">
|
||||
{{ list.title }}
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<template>
|
||||
<BaseButton class="button-link"><slot/></BaseButton>
|
||||
<BaseButton class="button-link">
|
||||
<slot />
|
||||
</BaseButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="isDone"
|
||||
class="is-done"
|
||||
:class="{ 'is-done--small': variant === 'small' }"
|
||||
>
|
||||
{{ $t('task.attributes.done') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="isDone"
|
||||
class="is-done"
|
||||
:class="{ 'is-done--small': variant === 'small' }"
|
||||
>
|
||||
{{ $t('task.attributes.done') }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,39 +1,63 @@
|
|||
<template>
|
||||
<div class="api-config">
|
||||
<div v-if="configureApi">
|
||||
<label class="label" for="api-url">{{ $t('apiConfig.url') }}</label>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input
|
||||
class="input"
|
||||
id="api-url"
|
||||
:placeholder="$t('apiConfig.urlPlaceholder')"
|
||||
required
|
||||
type="url"
|
||||
v-focus
|
||||
v-model="apiUrl"
|
||||
@keyup.enter="setApiUrl"
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button @click="setApiUrl" :disabled="apiUrl === '' || undefined">
|
||||
{{ $t('apiConfig.change') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api-url-info" v-else>
|
||||
<i18n-t keypath="apiConfig.use" scope="global">
|
||||
<span class="url" v-tooltip="apiUrl"> {{ apiDomain }} </span>
|
||||
</i18n-t>
|
||||
<br/>
|
||||
<ButtonLink class="api-config__change-button" @click="() => (configureApi = true)">{{ $t('apiConfig.change') }}</ButtonLink>
|
||||
</div>
|
||||
<div class="api-config">
|
||||
<div v-if="configureApi">
|
||||
<label
|
||||
class="label"
|
||||
for="api-url"
|
||||
>{{ $t('apiConfig.url') }}</label>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input
|
||||
id="api-url"
|
||||
v-model="apiUrl"
|
||||
v-focus
|
||||
class="input"
|
||||
:placeholder="$t('apiConfig.urlPlaceholder')"
|
||||
required
|
||||
type="url"
|
||||
@keyup.enter="setApiUrl"
|
||||
>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
:disabled="apiUrl === '' || undefined"
|
||||
@click="setApiUrl"
|
||||
>
|
||||
{{ $t('apiConfig.change') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="api-url-info"
|
||||
>
|
||||
<i18n-t
|
||||
keypath="apiConfig.use"
|
||||
scope="global"
|
||||
>
|
||||
<span
|
||||
v-tooltip="apiUrl"
|
||||
class="url"
|
||||
> {{ apiDomain }} </span>
|
||||
</i18n-t>
|
||||
<br>
|
||||
<ButtonLink
|
||||
class="api-config__change-button"
|
||||
@click="() => (configureApi = true)"
|
||||
>
|
||||
{{ $t('apiConfig.change') }}
|
||||
</ButtonLink>
|
||||
</div>
|
||||
|
||||
<message variant="danger" v-if="errorMsg !== ''" class="mt-2">
|
||||
{{ errorMsg }}
|
||||
</message>
|
||||
</div>
|
||||
<message
|
||||
v-if="errorMsg !== ''"
|
||||
variant="danger"
|
||||
class="mt-2"
|
||||
>
|
||||
{{ errorMsg }}
|
||||
</message>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,37 +1,46 @@
|
|||
<template>
|
||||
<div class="card" :class="{'has-no-shadow': !shadow}">
|
||||
<header class="card-header" v-if="title !== ''">
|
||||
<p class="card-header-title">
|
||||
{{ title }}
|
||||
</p>
|
||||
<BaseButton
|
||||
v-if="hasClose"
|
||||
class="card-header-icon"
|
||||
:aria-label="$t('misc.close')"
|
||||
@click="$emit('close')"
|
||||
v-tooltip="$t('misc.close')"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon :icon="closeIcon"/>
|
||||
</span>
|
||||
</BaseButton>
|
||||
</header>
|
||||
<div
|
||||
class="card-content loader-container"
|
||||
:class="{
|
||||
'p-0': !padding,
|
||||
'is-loading': loading
|
||||
}"
|
||||
>
|
||||
<div :class="{'content': hasContent}">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="card"
|
||||
:class="{'has-no-shadow': !shadow}"
|
||||
>
|
||||
<header
|
||||
v-if="title !== ''"
|
||||
class="card-header"
|
||||
>
|
||||
<p class="card-header-title">
|
||||
{{ title }}
|
||||
</p>
|
||||
<BaseButton
|
||||
v-if="hasClose"
|
||||
v-tooltip="$t('misc.close')"
|
||||
class="card-header-icon"
|
||||
:aria-label="$t('misc.close')"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon :icon="closeIcon" />
|
||||
</span>
|
||||
</BaseButton>
|
||||
</header>
|
||||
<div
|
||||
class="card-content loader-container"
|
||||
:class="{
|
||||
'p-0': !padding,
|
||||
'is-loading': loading
|
||||
}"
|
||||
>
|
||||
<div :class="{'content': hasContent}">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer v-if="$slots.footer" class="card-footer">
|
||||
<slot name="footer" />
|
||||
</footer>
|
||||
</div>
|
||||
<footer
|
||||
v-if="$slots.footer"
|
||||
class="card-footer"
|
||||
>
|
||||
<slot name="footer" />
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<span
|
||||
:style="{backgroundColor: color }"
|
||||
class="color-bubble"
|
||||
></span>
|
||||
<span
|
||||
:style="{backgroundColor: color }"
|
||||
class="color-bubble"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,46 +1,50 @@
|
|||
<template>
|
||||
<modal @close="$router.back()" :overflow="true" :wide="wide">
|
||||
<card
|
||||
:title="title"
|
||||
:shadow="false"
|
||||
:padding="false"
|
||||
class="has-text-left"
|
||||
:has-close="true"
|
||||
@close="$router.back()"
|
||||
:loading="loading"
|
||||
>
|
||||
<div class="p-4">
|
||||
<slot />
|
||||
</div>
|
||||
<modal
|
||||
:overflow="true"
|
||||
:wide="wide"
|
||||
@close="$router.back()"
|
||||
>
|
||||
<card
|
||||
:title="title"
|
||||
:shadow="false"
|
||||
:padding="false"
|
||||
class="has-text-left"
|
||||
:has-close="true"
|
||||
:loading="loading"
|
||||
@close="$router.back()"
|
||||
>
|
||||
<div class="p-4">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<slot name="footer">
|
||||
<x-button
|
||||
v-if="tertiary !== ''"
|
||||
:shadow="false"
|
||||
variant="tertiary"
|
||||
@click.prevent.stop="$emit('tertiary')"
|
||||
>
|
||||
{{ tertiary }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
@click.prevent.stop="$router.back()"
|
||||
>
|
||||
{{ $t('misc.cancel') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="primary"
|
||||
@click.prevent.stop="primary()"
|
||||
:icon="primaryIcon"
|
||||
:disabled="primaryDisabled || loading"
|
||||
>
|
||||
{{ primaryLabel || $t('misc.create') }}
|
||||
</x-button>
|
||||
</slot>
|
||||
</template>
|
||||
</card>
|
||||
</modal>
|
||||
<template #footer>
|
||||
<slot name="footer">
|
||||
<x-button
|
||||
v-if="tertiary !== ''"
|
||||
:shadow="false"
|
||||
variant="tertiary"
|
||||
@click.prevent.stop="$emit('tertiary')"
|
||||
>
|
||||
{{ tertiary }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
@click.prevent.stop="$router.back()"
|
||||
>
|
||||
{{ $t('misc.cancel') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="primary"
|
||||
:icon="primaryIcon"
|
||||
:disabled="primaryDisabled || loading"
|
||||
@click.prevent.stop="primary()"
|
||||
>
|
||||
{{ primaryLabel || $t('misc.create') }}
|
||||
</x-button>
|
||||
</slot>
|
||||
</template>
|
||||
</card>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
<template>
|
||||
<component
|
||||
:is="componentNodeName"
|
||||
v-bind="elementBindings"
|
||||
:to="to"
|
||||
class="dropdown-item">
|
||||
<span class="icon" v-if="icon">
|
||||
<icon :icon="icon"/>
|
||||
</span>
|
||||
<span>
|
||||
<slot></slot>
|
||||
</span>
|
||||
</component>
|
||||
<component
|
||||
:is="componentNodeName"
|
||||
v-bind="elementBindings"
|
||||
:to="to"
|
||||
class="dropdown-item"
|
||||
>
|
||||
<span
|
||||
v-if="icon"
|
||||
class="icon"
|
||||
>
|
||||
<icon :icon="icon" />
|
||||
</span>
|
||||
<span>
|
||||
<slot />
|
||||
</span>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,19 +1,35 @@
|
|||
<template>
|
||||
<div class="dropdown" ref="dropdown">
|
||||
<slot name="trigger" :close="close" :toggleOpen="toggleOpen">
|
||||
<BaseButton class="dropdown-trigger is-flex" @click="toggleOpen">
|
||||
<icon :icon="triggerIcon" class="icon"/>
|
||||
</BaseButton>
|
||||
</slot>
|
||||
<div
|
||||
ref="dropdown"
|
||||
class="dropdown"
|
||||
>
|
||||
<slot
|
||||
name="trigger"
|
||||
:close="close"
|
||||
:toggle-open="toggleOpen"
|
||||
>
|
||||
<BaseButton
|
||||
class="dropdown-trigger is-flex"
|
||||
@click="toggleOpen"
|
||||
>
|
||||
<icon
|
||||
:icon="triggerIcon"
|
||||
class="icon"
|
||||
/>
|
||||
</BaseButton>
|
||||
</slot>
|
||||
|
||||
<transition name="fade">
|
||||
<div class="dropdown-menu" v-if="open">
|
||||
<div class="dropdown-content">
|
||||
<slot :close="close"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="open"
|
||||
class="dropdown-menu"
|
||||
>
|
||||
<div class="dropdown-content">
|
||||
<slot :close="close" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
<template>
|
||||
<message variant="danger">
|
||||
<i18n-t keypath="loadingError.failed" scope="global">
|
||||
<ButtonLink @click="reload">{{ $t('loadingError.tryAgain') }}</ButtonLink>
|
||||
<ButtonLink href="https://vikunja.io/contact/">{{ $t('loadingError.contact') }}</ButtonLink>
|
||||
</i18n-t>
|
||||
</message>
|
||||
<message variant="danger">
|
||||
<i18n-t
|
||||
keypath="loadingError.failed"
|
||||
scope="global"
|
||||
>
|
||||
<ButtonLink @click="reload">
|
||||
{{ $t('loadingError.tryAgain') }}
|
||||
</ButtonLink>
|
||||
<ButtonLink href="https://vikunja.io/contact/">
|
||||
{{ $t('loadingError.contact') }}
|
||||
</ButtonLink>
|
||||
</i18n-t>
|
||||
</message>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,35 +1,49 @@
|
|||
<template>
|
||||
<modal @close="close()">
|
||||
<card class="has-background-white has-no-shadow keyboard-shortcuts" :title="$t('keyboardShortcuts.title')">
|
||||
<template v-for="(s, i) in shortcuts" :key="i">
|
||||
<h3>{{ $t(s.title) }}</h3>
|
||||
<modal @close="close()">
|
||||
<card
|
||||
class="has-background-white has-no-shadow keyboard-shortcuts"
|
||||
:title="$t('keyboardShortcuts.title')"
|
||||
>
|
||||
<template
|
||||
v-for="(s, i) in shortcuts"
|
||||
:key="i"
|
||||
>
|
||||
<h3>{{ $t(s.title) }}</h3>
|
||||
|
||||
<message class="mb-4" v-if="s.available">
|
||||
{{
|
||||
typeof s.available === 'undefined' ?
|
||||
$t('keyboardShortcuts.allPages') :
|
||||
(
|
||||
s.available($route)
|
||||
? $t('keyboardShortcuts.currentPageOnly')
|
||||
: $t('keyboardShortcuts.somePagesOnly')
|
||||
)
|
||||
}}
|
||||
</message>
|
||||
<message
|
||||
v-if="s.available"
|
||||
class="mb-4"
|
||||
>
|
||||
{{
|
||||
typeof s.available === 'undefined' ?
|
||||
$t('keyboardShortcuts.allPages') :
|
||||
(
|
||||
s.available($route)
|
||||
? $t('keyboardShortcuts.currentPageOnly')
|
||||
: $t('keyboardShortcuts.somePagesOnly')
|
||||
)
|
||||
}}
|
||||
</message>
|
||||
|
||||
<dl class="shortcut-list">
|
||||
<template v-for="(sc, si) in s.shortcuts" :key="si">
|
||||
<dt class="shortcut-title">{{ $t(sc.title) }}</dt>
|
||||
<shortcut
|
||||
class="shortcut-keys"
|
||||
is="dd"
|
||||
:keys="sc.keys"
|
||||
:combination="sc.combination && $t(`keyboardShortcuts.${sc.combination}`)"
|
||||
/>
|
||||
</template>
|
||||
</dl>
|
||||
</template>
|
||||
</card>
|
||||
</modal>
|
||||
<dl class="shortcut-list">
|
||||
<template
|
||||
v-for="(sc, si) in s.shortcuts"
|
||||
:key="si"
|
||||
>
|
||||
<dt class="shortcut-title">
|
||||
{{ $t(sc.title) }}
|
||||
</dt>
|
||||
<shortcut
|
||||
is="dd"
|
||||
class="shortcut-keys"
|
||||
:keys="sc.keys"
|
||||
:combination="sc.combination && $t(`keyboardShortcuts.${sc.combination}`)"
|
||||
/>
|
||||
</template>
|
||||
</dl>
|
||||
</template>
|
||||
</card>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
<template>
|
||||
<div class="legal-links">
|
||||
<BaseButton :href="imprintUrl" v-if="imprintUrl">{{ $t('navigation.imprint') }}</BaseButton>
|
||||
<span v-if="imprintUrl && privacyPolicyUrl"> | </span>
|
||||
<BaseButton :href="privacyPolicyUrl" v-if="privacyPolicyUrl">{{ $t('navigation.privacy') }}</BaseButton>
|
||||
</div>
|
||||
<div class="legal-links">
|
||||
<BaseButton
|
||||
v-if="imprintUrl"
|
||||
:href="imprintUrl"
|
||||
>
|
||||
{{ $t('navigation.imprint') }}
|
||||
</BaseButton>
|
||||
<span v-if="imprintUrl && privacyPolicyUrl"> | </span>
|
||||
<BaseButton
|
||||
v-if="privacyPolicyUrl"
|
||||
:href="privacyPolicyUrl"
|
||||
>
|
||||
{{ $t('navigation.privacy') }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="loader-container is-loading"></div>
|
||||
<div class="loader-container is-loading" />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
<template>
|
||||
<div class="message-wrapper">
|
||||
<div class="message" :class="[variant, textAlignClass]">
|
||||
<slot/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="message-wrapper">
|
||||
<div
|
||||
class="message"
|
||||
:class="[variant, textAlignClass]"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,66 +1,66 @@
|
|||
<template>
|
||||
<Teleport to="body">
|
||||
<!-- FIXME: transition should not be included in the modal -->
|
||||
<transition :name="transitionName">
|
||||
<section
|
||||
v-if="enabled"
|
||||
class="modal-mask"
|
||||
:class="[
|
||||
{ 'has-overflow': overflow },
|
||||
variant,
|
||||
]"
|
||||
ref="modal"
|
||||
v-bind="attrs"
|
||||
>
|
||||
<div
|
||||
class="modal-container"
|
||||
@click.self.prevent.stop="$emit('close')"
|
||||
v-shortcut="'Escape'"
|
||||
>
|
||||
<div
|
||||
class="modal-content"
|
||||
:class="{
|
||||
'has-overflow': overflow,
|
||||
'is-wide': wide
|
||||
}"
|
||||
>
|
||||
<BaseButton
|
||||
@click="$emit('close')"
|
||||
class="close"
|
||||
>
|
||||
<icon icon="times"/>
|
||||
</BaseButton>
|
||||
<Teleport to="body">
|
||||
<!-- FIXME: transition should not be included in the modal -->
|
||||
<transition :name="transitionName">
|
||||
<section
|
||||
v-if="enabled"
|
||||
ref="modal"
|
||||
class="modal-mask"
|
||||
:class="[
|
||||
{ 'has-overflow': overflow },
|
||||
variant,
|
||||
]"
|
||||
v-bind="attrs"
|
||||
>
|
||||
<div
|
||||
v-shortcut="'Escape'"
|
||||
class="modal-container"
|
||||
@click.self.prevent.stop="$emit('close')"
|
||||
>
|
||||
<div
|
||||
class="modal-content"
|
||||
:class="{
|
||||
'has-overflow': overflow,
|
||||
'is-wide': wide
|
||||
}"
|
||||
>
|
||||
<BaseButton
|
||||
class="close"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<icon icon="times" />
|
||||
</BaseButton>
|
||||
|
||||
<slot>
|
||||
<div class="header">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<div class="content">
|
||||
<slot name="text"></slot>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<x-button
|
||||
@click="$emit('close')"
|
||||
variant="tertiary"
|
||||
class="has-text-danger"
|
||||
>
|
||||
{{ $t('misc.cancel') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
@click="$emit('submit')"
|
||||
variant="primary"
|
||||
v-cy="'modalPrimary'"
|
||||
:shadow="false"
|
||||
>
|
||||
{{ $t('misc.doit') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</transition>
|
||||
</Teleport>
|
||||
<slot>
|
||||
<div class="header">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<slot name="text" />
|
||||
</div>
|
||||
<div class="actions">
|
||||
<x-button
|
||||
variant="tertiary"
|
||||
class="has-text-danger"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
{{ $t('misc.cancel') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
v-cy="'modalPrimary'"
|
||||
variant="primary"
|
||||
:shadow="false"
|
||||
@click="$emit('submit')"
|
||||
>
|
||||
{{ $t('misc.doit') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -1,28 +1,43 @@
|
|||
<template>
|
||||
<div class="no-auth-wrapper">
|
||||
<Logo class="logo" width="200" height="58"/>
|
||||
<div class="noauth-container">
|
||||
<section class="image" :class="{'has-message': motd !== ''}">
|
||||
<Message v-if="motd !== ''">
|
||||
{{ motd }}
|
||||
</Message>
|
||||
<h2 class="image-title">
|
||||
{{ $t('misc.welcomeBack') }}
|
||||
</h2>
|
||||
</section>
|
||||
<section class="content">
|
||||
<div>
|
||||
<h2 class="title" v-if="title">{{ title }}</h2>
|
||||
<api-config/>
|
||||
<Message v-if="motd !== ''" class="is-hidden-tablet mb-4">
|
||||
{{ motd }}
|
||||
</Message>
|
||||
<slot/>
|
||||
</div>
|
||||
<legal/>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="no-auth-wrapper">
|
||||
<Logo
|
||||
class="logo"
|
||||
width="200"
|
||||
height="58"
|
||||
/>
|
||||
<div class="noauth-container">
|
||||
<section
|
||||
class="image"
|
||||
:class="{'has-message': motd !== ''}"
|
||||
>
|
||||
<Message v-if="motd !== ''">
|
||||
{{ motd }}
|
||||
</Message>
|
||||
<h2 class="image-title">
|
||||
{{ $t('misc.welcomeBack') }}
|
||||
</h2>
|
||||
</section>
|
||||
<section class="content">
|
||||
<div>
|
||||
<h2
|
||||
v-if="title"
|
||||
class="title"
|
||||
>
|
||||
{{ title }}
|
||||
</h2>
|
||||
<api-config />
|
||||
<Message
|
||||
v-if="motd !== ''"
|
||||
class="is-hidden-tablet mb-4"
|
||||
>
|
||||
{{ motd }}
|
||||
</Message>
|
||||
<slot />
|
||||
</div>
|
||||
<legal />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<p class="has-text-centered has-text-grey is-italic p-4 mb-4">
|
||||
<slot></slot>
|
||||
</p>
|
||||
<p class="has-text-centered has-text-grey is-italic p-4 mb-4">
|
||||
<slot />
|
||||
</p>
|
||||
</template>
|
|
@ -1,41 +1,55 @@
|
|||
<template>
|
||||
<notifications position="bottom left" :max="2" class="global-notification">
|
||||
<template #body="{ item, close }">
|
||||
<!-- FIXME: overlay whole notification with button and add event listener on that button instead -->
|
||||
<div
|
||||
:class="[
|
||||
'vue-notification-template',
|
||||
'vue-notification',
|
||||
item.type,
|
||||
]"
|
||||
@click="close()"
|
||||
>
|
||||
<div v-if="item.title" class="notification-title">{{ item.title }}</div>
|
||||
<div class="notification-content">
|
||||
<template v-for="(t, k) in item.text" :key="k">{{ t }}<br /></template>
|
||||
</div>
|
||||
<div
|
||||
class="buttons is-right"
|
||||
v-if="
|
||||
item.data &&
|
||||
item.data.actions &&
|
||||
item.data.actions.length > 0
|
||||
"
|
||||
>
|
||||
<x-button
|
||||
:key="'action_' + i"
|
||||
@click="action.callback"
|
||||
:shadow="false"
|
||||
class="is-small"
|
||||
variant="secondary"
|
||||
v-for="(action, i) in item.data.actions"
|
||||
>
|
||||
{{ action.title }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</notifications>
|
||||
<notifications
|
||||
position="bottom left"
|
||||
:max="2"
|
||||
class="global-notification"
|
||||
>
|
||||
<template #body="{ item, close }">
|
||||
<!-- FIXME: overlay whole notification with button and add event listener on that button instead -->
|
||||
<div
|
||||
:class="[
|
||||
'vue-notification-template',
|
||||
'vue-notification',
|
||||
item.type,
|
||||
]"
|
||||
@click="close()"
|
||||
>
|
||||
<div
|
||||
v-if="item.title"
|
||||
class="notification-title"
|
||||
>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div class="notification-content">
|
||||
<template
|
||||
v-for="(t, k) in item.text"
|
||||
:key="k"
|
||||
>
|
||||
{{ t }}<br>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
item.data &&
|
||||
item.data.actions &&
|
||||
item.data.actions.length > 0
|
||||
"
|
||||
class="buttons is-right"
|
||||
>
|
||||
<x-button
|
||||
v-for="(action, i) in item.data.actions"
|
||||
:key="'action_' + i"
|
||||
:shadow="false"
|
||||
class="is-small"
|
||||
variant="secondary"
|
||||
@click="action.callback"
|
||||
>
|
||||
{{ action.title }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</notifications>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -1,37 +1,45 @@
|
|||
<template>
|
||||
<nav
|
||||
aria-label="pagination"
|
||||
class="pagination is-centered p-4"
|
||||
role="navigation"
|
||||
v-if="totalPages > 1"
|
||||
>
|
||||
<router-link
|
||||
:disabled="currentPage === 1 || undefined"
|
||||
:to="getRouteForPagination(currentPage - 1)"
|
||||
class="pagination-previous">
|
||||
{{ $t('misc.previous') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
:disabled="currentPage === totalPages || undefined"
|
||||
:to="getRouteForPagination(currentPage + 1)"
|
||||
class="pagination-next">
|
||||
{{ $t('misc.next') }}
|
||||
</router-link>
|
||||
<ul class="pagination-list">
|
||||
<li :key="`page-${i}`" v-for="(p, i) in pages">
|
||||
<span class="pagination-ellipsis" v-if="p.isEllipsis">…</span>
|
||||
<router-link
|
||||
v-else
|
||||
class="pagination-link"
|
||||
:aria-label="'Goto page ' + p.number"
|
||||
:class="{ 'is-current': p.number === currentPage }"
|
||||
:to="getRouteForPagination(p.number)"
|
||||
>
|
||||
{{ p.number }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav
|
||||
v-if="totalPages > 1"
|
||||
aria-label="pagination"
|
||||
class="pagination is-centered p-4"
|
||||
role="navigation"
|
||||
>
|
||||
<router-link
|
||||
:disabled="currentPage === 1 || undefined"
|
||||
:to="getRouteForPagination(currentPage - 1)"
|
||||
class="pagination-previous"
|
||||
>
|
||||
{{ $t('misc.previous') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
:disabled="currentPage === totalPages || undefined"
|
||||
:to="getRouteForPagination(currentPage + 1)"
|
||||
class="pagination-next"
|
||||
>
|
||||
{{ $t('misc.next') }}
|
||||
</router-link>
|
||||
<ul class="pagination-list">
|
||||
<li
|
||||
v-for="(p, i) in pages"
|
||||
:key="`page-${i}`"
|
||||
>
|
||||
<span
|
||||
v-if="p.isEllipsis"
|
||||
class="pagination-ellipsis"
|
||||
>…</span>
|
||||
<router-link
|
||||
v-else
|
||||
class="pagination-link"
|
||||
:aria-label="'Goto page ' + p.number"
|
||||
:class="{ 'is-current': p.number === currentPage }"
|
||||
:to="getRouteForPagination(p.number)"
|
||||
>
|
||||
{{ p.number }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
<template>
|
||||
<slot name="trigger" :isOpen="open" :toggle="toggle"></slot>
|
||||
<div class="popup" :class="{'is-open': open, 'has-overflow': props.hasOverflow && open}" ref="popup">
|
||||
<slot name="content" :isOpen="open"/>
|
||||
</div>
|
||||
<slot
|
||||
name="trigger"
|
||||
:is-open="open"
|
||||
:toggle="toggle"
|
||||
/>
|
||||
<div
|
||||
ref="popup"
|
||||
class="popup"
|
||||
:class="{'is-open': open, 'has-overflow': props.hasOverflow && open}"
|
||||
>
|
||||
<slot
|
||||
name="content"
|
||||
:is-open="open"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,43 +1,58 @@
|
|||
<template>
|
||||
<!-- This is a workaround to get the sw to "see" the to-be-cached version of the offline background image -->
|
||||
<div class="offline" style="height: 0;width: 0;"></div>
|
||||
<div class="app offline" v-if="!online">
|
||||
<div class="offline-message">
|
||||
<h1>{{ $t('offline.title') }}</h1>
|
||||
<p>{{ $t('offline.text') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else-if="ready">
|
||||
<slot/>
|
||||
</template>
|
||||
<section v-else-if="error !== ''">
|
||||
<no-auth-wrapper>
|
||||
<card>
|
||||
<p v-if="error === ERROR_NO_API_URL">
|
||||
{{ $t('ready.noApiUrlConfigured') }}
|
||||
</p>
|
||||
<message variant="danger" v-else>
|
||||
<p>
|
||||
{{ $t('ready.errorOccured') }}<br/>
|
||||
{{ error }}
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('ready.checkApiUrl') }}
|
||||
</p>
|
||||
</message>
|
||||
<api-config :configure-open="true" @found-api="load"/>
|
||||
</card>
|
||||
</no-auth-wrapper>
|
||||
</section>
|
||||
<transition name="fade">
|
||||
<section class="vikunja-loading" v-if="showLoading">
|
||||
<Logo class="logo"/>
|
||||
<p>
|
||||
<span class="loader-container is-loading-small is-loading"></span>
|
||||
{{ $t('ready.loading') }}
|
||||
</p>
|
||||
</section>
|
||||
</transition>
|
||||
<!-- This is a workaround to get the sw to "see" the to-be-cached version of the offline background image -->
|
||||
<div
|
||||
class="offline"
|
||||
style="height: 0;width: 0;"
|
||||
/>
|
||||
<div
|
||||
v-if="!online"
|
||||
class="app offline"
|
||||
>
|
||||
<div class="offline-message">
|
||||
<h1>{{ $t('offline.title') }}</h1>
|
||||
<p>{{ $t('offline.text') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else-if="ready">
|
||||
<slot />
|
||||
</template>
|
||||
<section v-else-if="error !== ''">
|
||||
<no-auth-wrapper>
|
||||
<card>
|
||||
<p v-if="error === ERROR_NO_API_URL">
|
||||
{{ $t('ready.noApiUrlConfigured') }}
|
||||
</p>
|
||||
<message
|
||||
v-else
|
||||
variant="danger"
|
||||
>
|
||||
<p>
|
||||
{{ $t('ready.errorOccured') }}<br>
|
||||
{{ error }}
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('ready.checkApiUrl') }}
|
||||
</p>
|
||||
</message>
|
||||
<api-config
|
||||
:configure-open="true"
|
||||
@found-api="load"
|
||||
/>
|
||||
</card>
|
||||
</no-auth-wrapper>
|
||||
</section>
|
||||
<transition name="fade">
|
||||
<section
|
||||
v-if="showLoading"
|
||||
class="vikunja-loading"
|
||||
>
|
||||
<Logo class="logo" />
|
||||
<p>
|
||||
<span class="loader-container is-loading-small is-loading" />
|
||||
{{ $t('ready.loading') }}
|
||||
</p>
|
||||
</section>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
<template>
|
||||
<component :is="is" class="shortcuts">
|
||||
<template v-for="(k, i) in keys" :key="i">
|
||||
<kbd>{{ k }}</kbd>
|
||||
<span v-if="i < keys.length - 1">{{ combination }}</span>
|
||||
</template>
|
||||
</component>
|
||||
<component
|
||||
:is="is"
|
||||
class="shortcuts"
|
||||
>
|
||||
<template
|
||||
v-for="(k, i) in keys"
|
||||
:key="i"
|
||||
>
|
||||
<kbd>{{ k }}</kbd>
|
||||
<span v-if="i < keys.length - 1">{{ combination }}</span>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
<template>
|
||||
<x-button
|
||||
v-if="type === 'button'"
|
||||
variant="secondary"
|
||||
:icon="iconName"
|
||||
v-tooltip="tooltipText"
|
||||
@click="changeSubscription"
|
||||
:disabled="disabled"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</x-button>
|
||||
<DropdownItem
|
||||
v-else-if="type === 'dropdown'"
|
||||
v-tooltip="tooltipText"
|
||||
@click="changeSubscription"
|
||||
:class="{'is-disabled': disabled}"
|
||||
:icon="iconName"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</DropdownItem>
|
||||
<BaseButton
|
||||
v-else
|
||||
v-tooltip="tooltipText"
|
||||
@click="changeSubscription"
|
||||
:class="{'is-disabled': disabled}"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon :icon="iconName"/>
|
||||
</span>
|
||||
{{ buttonText }}
|
||||
</BaseButton>
|
||||
<x-button
|
||||
v-if="type === 'button'"
|
||||
v-tooltip="tooltipText"
|
||||
variant="secondary"
|
||||
:icon="iconName"
|
||||
:disabled="disabled"
|
||||
@click="changeSubscription"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</x-button>
|
||||
<DropdownItem
|
||||
v-else-if="type === 'dropdown'"
|
||||
v-tooltip="tooltipText"
|
||||
:class="{'is-disabled': disabled}"
|
||||
:icon="iconName"
|
||||
@click="changeSubscription"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</DropdownItem>
|
||||
<BaseButton
|
||||
v-else
|
||||
v-tooltip="tooltipText"
|
||||
:class="{'is-disabled': disabled}"
|
||||
:disabled="disabled"
|
||||
@click="changeSubscription"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon :icon="iconName" />
|
||||
</span>
|
||||
{{ buttonText }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
<template>
|
||||
<div :class="{'is-inline': isInline}" class="user">
|
||||
<img
|
||||
:height="avatarSize"
|
||||
:src="getAvatarUrl(user, avatarSize)"
|
||||
:width="avatarSize"
|
||||
alt=""
|
||||
class="avatar"
|
||||
v-tooltip="getDisplayName(user)"/>
|
||||
<span class="username" v-if="showUsername">{{ getDisplayName(user) }}</span>
|
||||
</div>
|
||||
<div
|
||||
:class="{'is-inline': isInline}"
|
||||
class="user"
|
||||
>
|
||||
<img
|
||||
v-tooltip="getDisplayName(user)"
|
||||
:height="avatarSize"
|
||||
:src="getAvatarUrl(user, avatarSize)"
|
||||
:width="avatarSize"
|
||||
alt=""
|
||||
class="avatar"
|
||||
>
|
||||
<span
|
||||
v-if="showUsername"
|
||||
class="username"
|
||||
>{{ getDisplayName(user) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,57 +1,57 @@
|
|||
<template>
|
||||
<dropdown>
|
||||
<template v-if="namespace.isArchived">
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id } }"
|
||||
icon="archive"
|
||||
>
|
||||
{{ $t('menu.unarchive') }}
|
||||
</dropdown-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.edit', params: { id: namespace.id } }"
|
||||
icon="pen"
|
||||
>
|
||||
{{ $t('menu.edit') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.share', params: { namespaceId: namespace.id } }"
|
||||
icon="share-alt"
|
||||
>
|
||||
{{ $t('menu.share') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.create', params: { namespaceId: namespace.id } }"
|
||||
icon="plus"
|
||||
>
|
||||
{{ $t('menu.newList') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id } }"
|
||||
icon="archive"
|
||||
>
|
||||
{{ $t('menu.archive') }}
|
||||
</dropdown-item>
|
||||
<task-subscription
|
||||
v-if="subscription"
|
||||
class="has-no-shadow"
|
||||
:is-button="false"
|
||||
entity="namespace"
|
||||
:entity-id="namespace.id"
|
||||
:model-value="subscription"
|
||||
@update:model-value="sub => subscription = sub"
|
||||
type="dropdown"
|
||||
/>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.delete', params: { id: namespace.id } }"
|
||||
icon="trash-alt"
|
||||
class="has-text-danger"
|
||||
>
|
||||
{{ $t('menu.delete') }}
|
||||
</dropdown-item>
|
||||
</template>
|
||||
</dropdown>
|
||||
<dropdown>
|
||||
<template v-if="namespace.isArchived">
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id } }"
|
||||
icon="archive"
|
||||
>
|
||||
{{ $t('menu.unarchive') }}
|
||||
</dropdown-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.edit', params: { id: namespace.id } }"
|
||||
icon="pen"
|
||||
>
|
||||
{{ $t('menu.edit') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.share', params: { namespaceId: namespace.id } }"
|
||||
icon="share-alt"
|
||||
>
|
||||
{{ $t('menu.share') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.create', params: { namespaceId: namespace.id } }"
|
||||
icon="plus"
|
||||
>
|
||||
{{ $t('menu.newList') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id } }"
|
||||
icon="archive"
|
||||
>
|
||||
{{ $t('menu.archive') }}
|
||||
</dropdown-item>
|
||||
<task-subscription
|
||||
v-if="subscription"
|
||||
class="has-no-shadow"
|
||||
:is-button="false"
|
||||
entity="namespace"
|
||||
:entity-id="namespace.id"
|
||||
:model-value="subscription"
|
||||
type="dropdown"
|
||||
@update:model-value="sub => subscription = sub"
|
||||
/>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.delete', params: { id: namespace.id } }"
|
||||
icon="trash-alt"
|
||||
class="has-text-danger"
|
||||
>
|
||||
{{ $t('menu.delete') }}
|
||||
</dropdown-item>
|
||||
</template>
|
||||
</dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,49 +1,72 @@
|
|||
<template>
|
||||
<div class="notifications">
|
||||
<div class="is-flex is-justify-content-center">
|
||||
<BaseButton @click.stop="showNotifications = !showNotifications" class="trigger-button">
|
||||
<span class="unread-indicator" v-if="unreadNotifications > 0"></span>
|
||||
<icon icon="bell"/>
|
||||
</BaseButton>
|
||||
</div>
|
||||
<div class="notifications">
|
||||
<div class="is-flex is-justify-content-center">
|
||||
<BaseButton
|
||||
class="trigger-button"
|
||||
@click.stop="showNotifications = !showNotifications"
|
||||
>
|
||||
<span
|
||||
v-if="unreadNotifications > 0"
|
||||
class="unread-indicator"
|
||||
/>
|
||||
<icon icon="bell" />
|
||||
</BaseButton>
|
||||
</div>
|
||||
|
||||
<transition name="fade">
|
||||
<div class="notifications-list" v-if="showNotifications" ref="popup">
|
||||
<span class="head">{{ $t('notification.title') }}</span>
|
||||
<div
|
||||
v-for="(n, index) in notifications"
|
||||
:key="n.id"
|
||||
class="single-notification"
|
||||
>
|
||||
<div class="read-indicator" :class="{'read': n.readAt !== null}"></div>
|
||||
<user
|
||||
:user="n.notification.doer"
|
||||
:show-username="false"
|
||||
:avatar-size="16"
|
||||
v-if="n.notification.doer"/>
|
||||
<div class="detail">
|
||||
<div>
|
||||
<span class="has-text-weight-bold mr-1" v-if="n.notification.doer">
|
||||
{{ getDisplayName(n.notification.doer) }}
|
||||
</span>
|
||||
<BaseButton @click="() => to(n, index)()">
|
||||
{{ n.toText(userInfo) }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
<span class="created" v-tooltip="formatDateLong(n.created)">
|
||||
{{ formatDateSince(n.created) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="nothing" v-if="notifications.length === 0">
|
||||
{{ $t('notification.none') }}<br/>
|
||||
<span class="explainer">
|
||||
{{ $t('notification.explainer') }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="showNotifications"
|
||||
ref="popup"
|
||||
class="notifications-list"
|
||||
>
|
||||
<span class="head">{{ $t('notification.title') }}</span>
|
||||
<div
|
||||
v-for="(n, index) in notifications"
|
||||
:key="n.id"
|
||||
class="single-notification"
|
||||
>
|
||||
<div
|
||||
class="read-indicator"
|
||||
:class="{'read': n.readAt !== null}"
|
||||
/>
|
||||
<user
|
||||
v-if="n.notification.doer"
|
||||
:user="n.notification.doer"
|
||||
:show-username="false"
|
||||
:avatar-size="16"
|
||||
/>
|
||||
<div class="detail">
|
||||
<div>
|
||||
<span
|
||||
v-if="n.notification.doer"
|
||||
class="has-text-weight-bold mr-1"
|
||||
>
|
||||
{{ getDisplayName(n.notification.doer) }}
|
||||
</span>
|
||||
<BaseButton @click="() => to(n, index)()">
|
||||
{{ n.toText(userInfo) }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
<span
|
||||
v-tooltip="formatDateLong(n.created)"
|
||||
class="created"
|
||||
>
|
||||
{{ formatDateSince(n.created) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
v-if="notifications.length === 0"
|
||||
class="nothing"
|
||||
>
|
||||
{{ $t('notification.none') }}<br>
|
||||
<span class="explainer">
|
||||
{{ $t('notification.explainer') }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,55 +1,78 @@
|
|||
<template>
|
||||
<modal v-if="active" @close="closeQuickActions" :overflow="isNewTaskCommand">
|
||||
<div class="card quick-actions">
|
||||
<div class="action-input" :class="{'has-active-cmd': selectedCmd !== null}">
|
||||
<div class="active-cmd tag" v-if="selectedCmd !== null">
|
||||
{{ selectedCmd.title }}
|
||||
</div>
|
||||
<input
|
||||
v-focus
|
||||
class="input"
|
||||
:class="{'is-loading': loading}"
|
||||
v-model="query"
|
||||
:placeholder="placeholder"
|
||||
@keyup="search"
|
||||
ref="searchInput"
|
||||
@keydown.down.prevent="() => select(0, 0)"
|
||||
@keyup.prevent.delete="unselectCmd"
|
||||
@keyup.prevent.enter="doCmd"
|
||||
@keyup.prevent.esc="closeQuickActions"
|
||||
/>
|
||||
</div>
|
||||
<modal
|
||||
v-if="active"
|
||||
:overflow="isNewTaskCommand"
|
||||
@close="closeQuickActions"
|
||||
>
|
||||
<div class="card quick-actions">
|
||||
<div
|
||||
class="action-input"
|
||||
:class="{'has-active-cmd': selectedCmd !== null}"
|
||||
>
|
||||
<div
|
||||
v-if="selectedCmd !== null"
|
||||
class="active-cmd tag"
|
||||
>
|
||||
{{ selectedCmd.title }}
|
||||
</div>
|
||||
<input
|
||||
ref="searchInput"
|
||||
v-model="query"
|
||||
v-focus
|
||||
class="input"
|
||||
:class="{'is-loading': loading}"
|
||||
:placeholder="placeholder"
|
||||
@keyup="search"
|
||||
@keydown.down.prevent="() => select(0, 0)"
|
||||
@keyup.prevent.delete="unselectCmd"
|
||||
@keyup.prevent.enter="doCmd"
|
||||
@keyup.prevent.esc="closeQuickActions"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="help has-text-grey-light p-2" v-if="hintText !== '' && !isNewTaskCommand">
|
||||
{{ hintText }}
|
||||
</div>
|
||||
<div
|
||||
v-if="hintText !== '' && !isNewTaskCommand"
|
||||
class="help has-text-grey-light p-2"
|
||||
>
|
||||
{{ hintText }}
|
||||
</div>
|
||||
|
||||
<quick-add-magic class="p-2 modal-container-smaller" v-if="isNewTaskCommand"/>
|
||||
<quick-add-magic
|
||||
v-if="isNewTaskCommand"
|
||||
class="p-2 modal-container-smaller"
|
||||
/>
|
||||
|
||||
<div class="results" v-if="selectedCmd === null">
|
||||
<div v-for="(r, k) in results" :key="k" class="result">
|
||||
<span class="result-title">
|
||||
{{ r.title }}
|
||||
</span>
|
||||
<div class="result-items">
|
||||
<BaseButton
|
||||
v-for="(i, key) in r.items"
|
||||
:key="key"
|
||||
:ref="`result-${k}_${key}`"
|
||||
@keydown.up.prevent="() => select(k, key - 1)"
|
||||
@keydown.down.prevent="() => select(k, key + 1)"
|
||||
@click.prevent.stop="() => doAction(r.type, i)"
|
||||
@keyup.prevent.enter="() => doAction(r.type, i)"
|
||||
@keyup.prevent.esc="() => $refs.searchInput.focus()"
|
||||
:class="{'is-strikethrough': i.done}"
|
||||
>
|
||||
{{ i.title }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</modal>
|
||||
<div
|
||||
v-if="selectedCmd === null"
|
||||
class="results"
|
||||
>
|
||||
<div
|
||||
v-for="(r, k) in results"
|
||||
:key="k"
|
||||
class="result"
|
||||
>
|
||||
<span class="result-title">
|
||||
{{ r.title }}
|
||||
</span>
|
||||
<div class="result-items">
|
||||
<BaseButton
|
||||
v-for="(i, key) in r.items"
|
||||
:key="key"
|
||||
:ref="`result-${k}_${key}`"
|
||||
:class="{'is-strikethrough': i.done}"
|
||||
@keydown.up.prevent="() => select(k, key - 1)"
|
||||
@keydown.down.prevent="() => select(k, key + 1)"
|
||||
@click.prevent.stop="() => doAction(r.type, i)"
|
||||
@keyup.prevent.enter="() => doAction(r.type, i)"
|
||||
@keyup.prevent.esc="() => $refs.searchInput.focus()"
|
||||
>
|
||||
{{ i.title }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -92,7 +115,7 @@ const SEARCH_MODE_LISTS = 'lists'
|
|||
const SEARCH_MODE_TEAMS = 'teams'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'quick-actions',
|
||||
name: 'QuickActions',
|
||||
components: {
|
||||
BaseButton,
|
||||
QuickAddMagic,
|
||||
|
|
|
@ -1,190 +1,220 @@
|
|||
<template>
|
||||
<div>
|
||||
<p class="has-text-weight-bold">
|
||||
{{ $t('list.share.links.title') }}
|
||||
<span
|
||||
class="is-size-7 has-text-grey is-italic ml-3"
|
||||
v-tooltip="$t('list.share.links.explanation')">
|
||||
{{ $t('list.share.links.what') }}
|
||||
</span>
|
||||
</p>
|
||||
<div>
|
||||
<p class="has-text-weight-bold">
|
||||
{{ $t('list.share.links.title') }}
|
||||
<span
|
||||
v-tooltip="$t('list.share.links.explanation')"
|
||||
class="is-size-7 has-text-grey is-italic ml-3"
|
||||
>
|
||||
{{ $t('list.share.links.what') }}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div class="sharables-list">
|
||||
<x-button
|
||||
v-if="!(linkShares.length === 0 || showNewForm)"
|
||||
@click="showNewForm = true"
|
||||
icon="plus"
|
||||
class="mb-4">
|
||||
{{ $t('list.share.links.create') }}
|
||||
</x-button>
|
||||
<div class="sharables-list">
|
||||
<x-button
|
||||
v-if="!(linkShares.length === 0 || showNewForm)"
|
||||
icon="plus"
|
||||
class="mb-4"
|
||||
@click="showNewForm = true"
|
||||
>
|
||||
{{ $t('list.share.links.create') }}
|
||||
</x-button>
|
||||
|
||||
<div class="p-4" v-if="linkShares.length === 0 || showNewForm">
|
||||
<div class="field">
|
||||
<label class="label" for="linkShareRight">
|
||||
{{ $t('list.share.right.title') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select v-model="selectedRight" id="linkShareRight">
|
||||
<option :value="RIGHTS.READ">
|
||||
{{ $t('list.share.right.read') }}
|
||||
</option>
|
||||
<option :value="RIGHTS.READ_WRITE">
|
||||
{{ $t('list.share.right.readWrite') }}
|
||||
</option>
|
||||
<option :value="RIGHTS.ADMIN">
|
||||
{{ $t('list.share.right.admin') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="linkShareName">
|
||||
{{ $t('list.share.links.name') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<input
|
||||
id="linkShareName"
|
||||
class="input"
|
||||
:placeholder="$t('list.share.links.namePlaceholder')"
|
||||
v-tooltip="$t('list.share.links.nameExplanation')"
|
||||
v-model="name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="linkSharePassword">
|
||||
{{ $t('list.share.links.password') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<input
|
||||
id="linkSharePassword"
|
||||
type="password"
|
||||
class="input"
|
||||
:placeholder="$t('user.auth.passwordPlaceholder')"
|
||||
v-tooltip="$t('list.share.links.passwordExplanation')"
|
||||
v-model="password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<x-button @click="add(listId)" icon="plus">
|
||||
{{ $t('list.share.share') }}
|
||||
</x-button>
|
||||
</div>
|
||||
<div
|
||||
v-if="linkShares.length === 0 || showNewForm"
|
||||
class="p-4"
|
||||
>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="linkShareRight"
|
||||
>
|
||||
{{ $t('list.share.right.title') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select
|
||||
id="linkShareRight"
|
||||
v-model="selectedRight"
|
||||
>
|
||||
<option :value="RIGHTS.READ">
|
||||
{{ $t('list.share.right.read') }}
|
||||
</option>
|
||||
<option :value="RIGHTS.READ_WRITE">
|
||||
{{ $t('list.share.right.readWrite') }}
|
||||
</option>
|
||||
<option :value="RIGHTS.ADMIN">
|
||||
{{ $t('list.share.right.admin') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="linkShareName"
|
||||
>
|
||||
{{ $t('list.share.links.name') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<input
|
||||
id="linkShareName"
|
||||
v-model="name"
|
||||
v-tooltip="$t('list.share.links.nameExplanation')"
|
||||
class="input"
|
||||
:placeholder="$t('list.share.links.namePlaceholder')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="linkSharePassword"
|
||||
>
|
||||
{{ $t('list.share.links.password') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<input
|
||||
id="linkSharePassword"
|
||||
v-model="password"
|
||||
v-tooltip="$t('list.share.links.passwordExplanation')"
|
||||
type="password"
|
||||
class="input"
|
||||
:placeholder="$t('user.auth.passwordPlaceholder')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<x-button
|
||||
icon="plus"
|
||||
@click="add(listId)"
|
||||
>
|
||||
{{ $t('list.share.share') }}
|
||||
</x-button>
|
||||
</div>
|
||||
|
||||
<table
|
||||
class="table has-actions is-striped is-hoverable is-fullwidth link-share-list"
|
||||
v-if="linkShares.length > 0"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{{ $t('list.share.links.view') }}</th>
|
||||
<th>{{ $t('list.share.attributes.delete') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr :key="s.id" v-for="s in linkShares">
|
||||
<td>
|
||||
<p class="mb-2 is-italic" v-if="s.name !== ''">
|
||||
{{ s.name }}
|
||||
</p>
|
||||
<table
|
||||
v-if="linkShares.length > 0"
|
||||
class="table has-actions is-striped is-hoverable is-fullwidth link-share-list"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>{{ $t('list.share.links.view') }}</th>
|
||||
<th>{{ $t('list.share.attributes.delete') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="s in linkShares"
|
||||
:key="s.id"
|
||||
>
|
||||
<td>
|
||||
<p
|
||||
v-if="s.name !== ''"
|
||||
class="mb-2 is-italic"
|
||||
>
|
||||
{{ s.name }}
|
||||
</p>
|
||||
|
||||
<p class="mb-2">
|
||||
<i18n-t keypath="list.share.links.sharedBy" scope="global">
|
||||
<strong>{{ getDisplayName(s.sharedBy) }}</strong>
|
||||
</i18n-t>
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
<i18n-t
|
||||
keypath="list.share.links.sharedBy"
|
||||
scope="global"
|
||||
>
|
||||
<strong>{{ getDisplayName(s.sharedBy) }}</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>
|
||||
<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="control">
|
||||
<input
|
||||
:value="getShareLink(s.hash, selectedView[s.id])"
|
||||
class="input"
|
||||
readonly
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
@click="copy(getShareLink(s.hash, selectedView[s.id]))"
|
||||
:shadow="false"
|
||||
v-tooltip="$t('misc.copy')"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon icon="paste"/>
|
||||
</span>
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="select">
|
||||
<select v-model="selectedView[s.id]">
|
||||
<option
|
||||
v-for="(title, key) in availableViews"
|
||||
:value="key"
|
||||
:key="key">
|
||||
{{ title }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<td class="actions">
|
||||
<x-button
|
||||
@click="
|
||||
() => {
|
||||
linkIdToDelete = s.id
|
||||
showDeleteModal = true
|
||||
}
|
||||
"
|
||||
class="is-danger"
|
||||
icon="trash-alt"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="field has-addons no-input-mobile">
|
||||
<div class="control">
|
||||
<input
|
||||
:value="getShareLink(s.hash, selectedView[s.id])"
|
||||
class="input"
|
||||
readonly
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
v-tooltip="$t('misc.copy')"
|
||||
:shadow="false"
|
||||
@click="copy(getShareLink(s.hash, selectedView[s.id]))"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon icon="paste" />
|
||||
</span>
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="select">
|
||||
<select v-model="selectedView[s.id]">
|
||||
<option
|
||||
v-for="(title, key) in availableViews"
|
||||
:key="key"
|
||||
:value="key"
|
||||
>
|
||||
{{ title }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<td class="actions">
|
||||
<x-button
|
||||
class="is-danger"
|
||||
icon="trash-alt"
|
||||
@click="
|
||||
() => {
|
||||
linkIdToDelete = s.id
|
||||
showDeleteModal = true
|
||||
}
|
||||
"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<transition name="modal">
|
||||
<modal
|
||||
@close="showDeleteModal = false"
|
||||
@submit="remove(listId)"
|
||||
v-if="showDeleteModal"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{ $t('list.share.links.remove') }}</span>
|
||||
</template>
|
||||
<transition name="modal">
|
||||
<modal
|
||||
v-if="showDeleteModal"
|
||||
@close="showDeleteModal = false"
|
||||
@submit="remove(listId)"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{ $t('list.share.links.remove') }}</span>
|
||||
</template>
|
||||
|
||||
<template #text>
|
||||
<p>{{ $t('list.share.links.removeText') }}</p>
|
||||
</template>
|
||||
</modal>
|
||||
</transition>
|
||||
</div>
|
||||
<template #text>
|
||||
<p>{{ $t('list.share.links.removeText') }}</p>
|
||||
</template>
|
||||
</modal>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,139 +1,150 @@
|
|||
<template>
|
||||
<div>
|
||||
<p class="has-text-weight-bold">
|
||||
{{ $t('list.share.userTeam.shared', {type: shareTypeNames}) }}
|
||||
</p>
|
||||
<div v-if="userIsAdmin">
|
||||
<div class="field has-addons">
|
||||
<p
|
||||
class="control is-expanded"
|
||||
:class="{ 'is-loading': searchService.loading }"
|
||||
>
|
||||
<Multiselect
|
||||
:loading="searchService.loading"
|
||||
:placeholder="$t('misc.searchPlaceholder')"
|
||||
@search="find"
|
||||
:search-results="found"
|
||||
:label="searchLabel"
|
||||
v-model="sharable"
|
||||
/>
|
||||
</p>
|
||||
<p class="control">
|
||||
<x-button @click="add()">{{ $t('list.share.share') }}</x-button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="has-text-weight-bold">
|
||||
{{ $t('list.share.userTeam.shared', {type: shareTypeNames}) }}
|
||||
</p>
|
||||
<div v-if="userIsAdmin">
|
||||
<div class="field has-addons">
|
||||
<p
|
||||
class="control is-expanded"
|
||||
:class="{ 'is-loading': searchService.loading }"
|
||||
>
|
||||
<Multiselect
|
||||
v-model="sharable"
|
||||
:loading="searchService.loading"
|
||||
:placeholder="$t('misc.searchPlaceholder')"
|
||||
:search-results="found"
|
||||
:label="searchLabel"
|
||||
@search="find"
|
||||
/>
|
||||
</p>
|
||||
<p class="control">
|
||||
<x-button @click="add()">
|
||||
{{ $t('list.share.share') }}
|
||||
</x-button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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'">
|
||||
<td>{{ getDisplayName(s) }}</td>
|
||||
<td>
|
||||
<template v-if="s.id === userInfo.id">
|
||||
<b class="is-success">{{ $t('list.share.userTeam.you') }}</b>
|
||||
</template>
|
||||
</td>
|
||||
</template>
|
||||
<template v-if="shareType === 'team'">
|
||||
<td>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'teams.edit',
|
||||
params: { id: s.id },
|
||||
}"
|
||||
>
|
||||
{{ s.name }}
|
||||
</router-link>
|
||||
</td>
|
||||
</template>
|
||||
<td class="type">
|
||||
<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>
|
||||
</td>
|
||||
<td class="actions" v-if="userIsAdmin">
|
||||
<div class="select">
|
||||
<select
|
||||
@change="toggleType(s)"
|
||||
class="mr-2"
|
||||
v-model="selectedRight[s.id]"
|
||||
>
|
||||
<option
|
||||
:selected="s.right === RIGHTS.READ"
|
||||
:value="RIGHTS.READ"
|
||||
>
|
||||
{{ $t('list.share.right.read') }}
|
||||
</option>
|
||||
<option
|
||||
:selected="s.right === RIGHTS.READ_WRITE"
|
||||
:value="RIGHTS.READ_WRITE"
|
||||
>
|
||||
{{ $t('list.share.right.readWrite') }}
|
||||
</option>
|
||||
<option
|
||||
:selected="s.right === RIGHTS.ADMIN"
|
||||
:value="RIGHTS.ADMIN"
|
||||
>
|
||||
{{ $t('list.share.right.admin') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<x-button
|
||||
@click="
|
||||
() => {
|
||||
sharable = s
|
||||
showDeleteModal = true
|
||||
}
|
||||
"
|
||||
class="is-danger"
|
||||
icon="trash-alt"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table
|
||||
v-if="sharables.length > 0"
|
||||
class="table has-actions is-striped is-hoverable is-fullwidth mb-4"
|
||||
>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="s in sharables"
|
||||
:key="s.id"
|
||||
>
|
||||
<template v-if="shareType === 'user'">
|
||||
<td>{{ getDisplayName(s) }}</td>
|
||||
<td>
|
||||
<template v-if="s.id === userInfo.id">
|
||||
<b class="is-success">{{ $t('list.share.userTeam.you') }}</b>
|
||||
</template>
|
||||
</td>
|
||||
</template>
|
||||
<template v-if="shareType === 'team'">
|
||||
<td>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'teams.edit',
|
||||
params: { id: s.id },
|
||||
}"
|
||||
>
|
||||
{{ s.name }}
|
||||
</router-link>
|
||||
</td>
|
||||
</template>
|
||||
<td class="type">
|
||||
<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>
|
||||
</td>
|
||||
<td
|
||||
v-if="userIsAdmin"
|
||||
class="actions"
|
||||
>
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="selectedRight[s.id]"
|
||||
class="mr-2"
|
||||
@change="toggleType(s)"
|
||||
>
|
||||
<option
|
||||
:selected="s.right === RIGHTS.READ"
|
||||
:value="RIGHTS.READ"
|
||||
>
|
||||
{{ $t('list.share.right.read') }}
|
||||
</option>
|
||||
<option
|
||||
:selected="s.right === RIGHTS.READ_WRITE"
|
||||
:value="RIGHTS.READ_WRITE"
|
||||
>
|
||||
{{ $t('list.share.right.readWrite') }}
|
||||
</option>
|
||||
<option
|
||||
:selected="s.right === RIGHTS.ADMIN"
|
||||
:value="RIGHTS.ADMIN"
|
||||
>
|
||||
{{ $t('list.share.right.admin') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<x-button
|
||||
class="is-danger"
|
||||
icon="trash-alt"
|
||||
@click="
|
||||
() => {
|
||||
sharable = s
|
||||
showDeleteModal = true
|
||||
}
|
||||
"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<nothing v-else>
|
||||
{{ $t('list.share.userTeam.notShared', {type: shareTypeNames}) }}
|
||||
</nothing>
|
||||
<nothing v-else>
|
||||
{{ $t('list.share.userTeam.notShared', {type: shareTypeNames}) }}
|
||||
</nothing>
|
||||
|
||||
<transition name="modal">
|
||||
<modal
|
||||
@close="showDeleteModal = false"
|
||||
@submit="deleteSharable()"
|
||||
v-if="showDeleteModal"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{
|
||||
$t('list.share.userTeam.removeHeader', {type: shareTypeName, sharable: sharableName})
|
||||
}}</span>
|
||||
</template>
|
||||
<template #text>
|
||||
<p>{{ $t('list.share.userTeam.removeText', {type: shareTypeName, sharable: sharableName}) }}</p>
|
||||
</template>
|
||||
</modal>
|
||||
</transition>
|
||||
</div>
|
||||
<transition name="modal">
|
||||
<modal
|
||||
v-if="showDeleteModal"
|
||||
@close="showDeleteModal = false"
|
||||
@submit="deleteSharable()"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{
|
||||
$t('list.share.userTeam.removeHeader', {type: shareTypeName, sharable: sharableName})
|
||||
}}</span>
|
||||
</template>
|
||||
<template #text>
|
||||
<p>{{ $t('list.share.userTeam.removeText', {type: shareTypeName, sharable: sharableName}) }}</p>
|
||||
</template>
|
||||
</modal>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {name: 'userTeamShare'}
|
||||
export default {name: 'UserTeamShare'}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,43 +1,46 @@
|
|||
<template>
|
||||
<div class="task-add">
|
||||
<div class="field is-grouped">
|
||||
<p class="control has-icons-left is-expanded">
|
||||
<textarea
|
||||
:disabled="loading || undefined"
|
||||
class="add-task-textarea input"
|
||||
:class="{'textarea-empty': newTaskTitle === ''}"
|
||||
:placeholder="$t('list.list.addPlaceholder')"
|
||||
rows="1"
|
||||
v-focus
|
||||
v-model="newTaskTitle"
|
||||
ref="newTaskInput"
|
||||
@keyup="resetEmptyTitleError"
|
||||
@keydown.enter="handleEnter"
|
||||
/>
|
||||
<span class="icon is-small is-left">
|
||||
<icon icon="tasks"/>
|
||||
</span>
|
||||
</p>
|
||||
<p class="control">
|
||||
<x-button
|
||||
class="add-task-button"
|
||||
:disabled="newTaskTitle === '' || loading || undefined"
|
||||
@click="addTask()"
|
||||
icon="plus"
|
||||
:loading="loading"
|
||||
:aria-label="$t('list.list.add')"
|
||||
>
|
||||
<span class="button-text">
|
||||
{{ $t('list.list.add') }}
|
||||
</span>
|
||||
</x-button>
|
||||
</p>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errorMessage !== ''">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
<quick-add-magic v-else/>
|
||||
</div>
|
||||
<div class="task-add">
|
||||
<div class="field is-grouped">
|
||||
<p class="control has-icons-left is-expanded">
|
||||
<textarea
|
||||
ref="newTaskInput"
|
||||
v-model="newTaskTitle"
|
||||
v-focus
|
||||
:disabled="loading || undefined"
|
||||
class="add-task-textarea input"
|
||||
:class="{'textarea-empty': newTaskTitle === ''}"
|
||||
:placeholder="$t('list.list.addPlaceholder')"
|
||||
rows="1"
|
||||
@keyup="resetEmptyTitleError"
|
||||
@keydown.enter="handleEnter"
|
||||
/>
|
||||
<span class="icon is-small is-left">
|
||||
<icon icon="tasks" />
|
||||
</span>
|
||||
</p>
|
||||
<p class="control">
|
||||
<x-button
|
||||
class="add-task-button"
|
||||
:disabled="newTaskTitle === '' || loading || undefined"
|
||||
icon="plus"
|
||||
:loading="loading"
|
||||
:aria-label="$t('list.list.add')"
|
||||
@click="addTask()"
|
||||
>
|
||||
<span class="button-text">
|
||||
{{ $t('list.list.add') }}
|
||||
</span>
|
||||
</x-button>
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
v-if="errorMessage !== ''"
|
||||
class="help is-danger"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
<quick-add-magic v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,78 +1,84 @@
|
|||
<template>
|
||||
<card
|
||||
class="taskedit"
|
||||
:title="$t('list.list.editTask')"
|
||||
@close="$emit('close')"
|
||||
:has-close="true"
|
||||
>
|
||||
<form @submit.prevent="editTaskSubmit()">
|
||||
<div class="field">
|
||||
<label class="label" for="tasktext">{{ $t('task.attributes.title') }}</label>
|
||||
<div class="control">
|
||||
<input
|
||||
:class="{ disabled: taskService.loading }"
|
||||
:disabled="taskService.loading || undefined"
|
||||
@change="editTaskSubmit()"
|
||||
class="input"
|
||||
id="tasktext"
|
||||
type="text"
|
||||
v-focus
|
||||
v-model="taskEditTask.title"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="taskdescription">{{ $t('task.attributes.description') }}</label>
|
||||
<div class="control">
|
||||
<editor
|
||||
:preview-is-default="false"
|
||||
id="taskdescription"
|
||||
:placeholder="$t('task.description.placeholder')"
|
||||
v-if="editorActive"
|
||||
v-model="taskEditTask.description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<card
|
||||
class="taskedit"
|
||||
:title="$t('list.list.editTask')"
|
||||
:has-close="true"
|
||||
@close="$emit('close')"
|
||||
>
|
||||
<form @submit.prevent="editTaskSubmit()">
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="tasktext"
|
||||
>{{ $t('task.attributes.title') }}</label>
|
||||
<div class="control">
|
||||
<input
|
||||
id="tasktext"
|
||||
v-model="taskEditTask.title"
|
||||
v-focus
|
||||
:class="{ disabled: taskService.loading }"
|
||||
:disabled="taskService.loading || undefined"
|
||||
class="input"
|
||||
type="text"
|
||||
@change="editTaskSubmit()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="taskdescription"
|
||||
>{{ $t('task.attributes.description') }}</label>
|
||||
<div class="control">
|
||||
<editor
|
||||
v-if="editorActive"
|
||||
id="taskdescription"
|
||||
v-model="taskEditTask.description"
|
||||
:preview-is-default="false"
|
||||
:placeholder="$t('task.description.placeholder')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<strong>{{ $t('task.attributes.reminders') }}</strong>
|
||||
<reminders
|
||||
v-model="taskEditTask.reminderDates"
|
||||
@update:model-value="editTaskSubmit()"
|
||||
/>
|
||||
<strong>{{ $t('task.attributes.reminders') }}</strong>
|
||||
<reminders
|
||||
v-model="taskEditTask.reminderDates"
|
||||
@update:model-value="editTaskSubmit()"
|
||||
/>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.labels') }}</label>
|
||||
<div class="control">
|
||||
<edit-labels
|
||||
:task-id="taskEditTask.id"
|
||||
v-model="taskEditTask.labels"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.labels') }}</label>
|
||||
<div class="control">
|
||||
<edit-labels
|
||||
v-model="taskEditTask.labels"
|
||||
:task-id="taskEditTask.id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.color') }}</label>
|
||||
<div class="control">
|
||||
<color-picker v-model="taskEditTask.hexColor" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('task.attributes.color') }}</label>
|
||||
<div class="control">
|
||||
<color-picker v-model="taskEditTask.hexColor" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<x-button
|
||||
:loading="taskService.loading"
|
||||
class="is-fullwidth"
|
||||
@click="editTaskSubmit()"
|
||||
>
|
||||
{{ $t('misc.save') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
:loading="taskService.loading"
|
||||
class="is-fullwidth"
|
||||
@click="editTaskSubmit()"
|
||||
>
|
||||
{{ $t('misc.save') }}
|
||||
</x-button>
|
||||
|
||||
<router-link
|
||||
class="mt-2 has-text-centered is-block"
|
||||
:to="taskDetailRoute"
|
||||
>
|
||||
{{ $t('task.openDetail') }}
|
||||
</router-link>
|
||||
</form>
|
||||
</card>
|
||||
<router-link
|
||||
class="mt-2 has-text-centered is-block"
|
||||
:to="taskDetailRoute"
|
||||
>
|
||||
{{ $t('task.openDetail') }}
|
||||
</router-link>
|
||||
</form>
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,174 +1,196 @@
|
|||
<template>
|
||||
<div class="gantt-chart">
|
||||
<div class="filter-container">
|
||||
<div class="items">
|
||||
<filter-popup
|
||||
v-model="params"
|
||||
@update:modelValue="loadTasks()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dates">
|
||||
<template v-for="(y, yk) in days" :key="yk + 'year'">
|
||||
<div class="months">
|
||||
<div
|
||||
:key="mk + 'month'"
|
||||
class="month"
|
||||
v-for="(m, mk) in days[yk]"
|
||||
>
|
||||
{{ formatMonthAndYear(yk, parseInt(mk) + 1) }}
|
||||
<div class="days">
|
||||
<div
|
||||
:class="{ today: d.toDateString() === now.toDateString() }"
|
||||
:key="dk + 'day'"
|
||||
:style="{ width: dayWidth + 'px' }"
|
||||
class="day"
|
||||
v-for="(d, dk) in days[yk][mk]"
|
||||
>
|
||||
<span class="theday" v-if="dayWidth > 25">
|
||||
{{ d.getDate() }}
|
||||
</span>
|
||||
<span class="weekday" v-if="dayWidth > 25">
|
||||
{{
|
||||
d.toLocaleString('en-us', {
|
||||
weekday: 'short',
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div :style="{ width: fullWidth + 'px' }" class="tasks">
|
||||
<div
|
||||
v-for="(t, k) in theTasks"
|
||||
:key="t ? t.id : 0"
|
||||
:style="{
|
||||
background:
|
||||
'repeating-linear-gradient(90deg, #ededed, #ededed 1px, ' +
|
||||
(k % 2 === 0
|
||||
? '#fafafa 1px, #fafafa '
|
||||
: '#fff 1px, #fff ') +
|
||||
dayWidth +
|
||||
'px)',
|
||||
}"
|
||||
class="row"
|
||||
>
|
||||
<VueDragResize
|
||||
:class="{
|
||||
done: t ? t.done : false,
|
||||
'is-current-edit': taskToEdit !== null && taskToEdit.id === t.id,
|
||||
'has-light-text': !colorIsDark(t.getHexColor()),
|
||||
'has-dark-text': colorIsDark(t.getHexColor()),
|
||||
}"
|
||||
:gridX="dayWidth"
|
||||
:h="31"
|
||||
:isActive="canWrite"
|
||||
:minw="dayWidth"
|
||||
:parentLimitation="true"
|
||||
:parentW="fullWidth"
|
||||
:snapToGrid="true"
|
||||
:sticks="['mr', 'ml']"
|
||||
:style="{
|
||||
'border-color': t.getHexColor(),
|
||||
'background-color': t.getHexColor(),
|
||||
}"
|
||||
:w="t.durationDays * dayWidth"
|
||||
:x="t.offsetDays * dayWidth - 6"
|
||||
:y="0"
|
||||
@dragstop="(e) => resizeTask(t, e)"
|
||||
@resizestop="(e) => resizeTask(t, e)"
|
||||
axis="x"
|
||||
class="task"
|
||||
>
|
||||
<span
|
||||
:class="{
|
||||
'has-high-priority': t.priority >= priorities.HIGH,
|
||||
'has-not-so-high-priority':
|
||||
t.priority === priorities.HIGH,
|
||||
'has-super-high-priority':
|
||||
t.priority === priorities.DO_NOW,
|
||||
}"
|
||||
>
|
||||
{{ t.title }}
|
||||
</span>
|
||||
<priority-label :priority="t.priority" :done="t.done"/>
|
||||
<!-- using the key here forces vue to use the updated version model and not the response returned by the api -->
|
||||
<!-- FIXME: add label -->
|
||||
<BaseButton @click="editTask(theTasks[k])" class="edit-toggle">
|
||||
<icon icon="pen"/>
|
||||
</BaseButton>
|
||||
</VueDragResize>
|
||||
</div>
|
||||
<template v-if="showTaskswithoutDates">
|
||||
<div
|
||||
:key="t.id"
|
||||
:style="{
|
||||
background:
|
||||
'repeating-linear-gradient(90deg, #ededed, #ededed 1px, ' +
|
||||
(k % 2 === 0
|
||||
? '#fafafa 1px, #fafafa '
|
||||
: '#fff 1px, #fff ') +
|
||||
dayWidth +
|
||||
'px)',
|
||||
}"
|
||||
class="row"
|
||||
v-for="(t, k) in tasksWithoutDates"
|
||||
>
|
||||
<VueDragResize
|
||||
:gridX="dayWidth"
|
||||
:h="31"
|
||||
:isActive="canWrite"
|
||||
:minw="dayWidth"
|
||||
:parentLimitation="true"
|
||||
:parentW="fullWidth"
|
||||
:snapToGrid="true"
|
||||
:sticks="['mr', 'ml']"
|
||||
:x="dayOffsetUntilToday * dayWidth - 6"
|
||||
:y="0"
|
||||
@dragstop="(e) => resizeTask(t, e)"
|
||||
@resizestop="(e) => resizeTask(t, e)"
|
||||
axis="x"
|
||||
class="task nodate"
|
||||
v-tooltip="$t('list.gantt.noDates')"
|
||||
>
|
||||
<span>{{ t.title }}</span>
|
||||
</VueDragResize>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<form
|
||||
@submit.prevent="addNewTask()"
|
||||
class="add-new-task"
|
||||
v-if="canWrite"
|
||||
>
|
||||
<transition name="width">
|
||||
<input
|
||||
@blur="hideCrateNewTask"
|
||||
@keyup.esc="newTaskFieldActive = false"
|
||||
class="input"
|
||||
ref="newTaskTitleField"
|
||||
type="text"
|
||||
v-if="newTaskFieldActive"
|
||||
v-model="newTaskTitle"
|
||||
/>
|
||||
</transition>
|
||||
<x-button @click="showCreateNewTask" :shadow="false" icon="plus">
|
||||
{{ $t('list.list.newTaskCta') }}
|
||||
</x-button>
|
||||
</form>
|
||||
<transition name="fade">
|
||||
<edit-task
|
||||
v-if="isTaskEdit"
|
||||
class="taskedit"
|
||||
:title="$t('list.list.editTask')"
|
||||
@close="() => {isTaskEdit = false;taskToEdit = null}"
|
||||
:task="taskToEdit"
|
||||
/>
|
||||
</transition>
|
||||
</div>
|
||||
<div class="gantt-chart">
|
||||
<div class="filter-container">
|
||||
<div class="items">
|
||||
<filter-popup
|
||||
v-model="params"
|
||||
@update:modelValue="loadTasks()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dates">
|
||||
<template
|
||||
v-for="(y, yk) in days"
|
||||
:key="yk + 'year'"
|
||||
>
|
||||
<div class="months">
|
||||
<div
|
||||
v-for="(m, mk) in days[yk]"
|
||||
:key="mk + 'month'"
|
||||
class="month"
|
||||
>
|
||||
{{ formatMonthAndYear(yk, parseInt(mk) + 1) }}
|
||||
<div class="days">
|
||||
<div
|
||||
v-for="(d, dk) in days[yk][mk]"
|
||||
:key="dk + 'day'"
|
||||
:class="{ today: d.toDateString() === now.toDateString() }"
|
||||
:style="{ width: dayWidth + 'px' }"
|
||||
class="day"
|
||||
>
|
||||
<span
|
||||
v-if="dayWidth > 25"
|
||||
class="theday"
|
||||
>
|
||||
{{ d.getDate() }}
|
||||
</span>
|
||||
<span
|
||||
v-if="dayWidth > 25"
|
||||
class="weekday"
|
||||
>
|
||||
{{
|
||||
d.toLocaleString('en-us', {
|
||||
weekday: 'short',
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
:style="{ width: fullWidth + 'px' }"
|
||||
class="tasks"
|
||||
>
|
||||
<div
|
||||
v-for="(t, k) in theTasks"
|
||||
:key="t ? t.id : 0"
|
||||
:style="{
|
||||
background:
|
||||
'repeating-linear-gradient(90deg, #ededed, #ededed 1px, ' +
|
||||
(k % 2 === 0
|
||||
? '#fafafa 1px, #fafafa '
|
||||
: '#fff 1px, #fff ') +
|
||||
dayWidth +
|
||||
'px)',
|
||||
}"
|
||||
class="row"
|
||||
>
|
||||
<VueDragResize
|
||||
:class="{
|
||||
done: t ? t.done : false,
|
||||
'is-current-edit': taskToEdit !== null && taskToEdit.id === t.id,
|
||||
'has-light-text': !colorIsDark(t.getHexColor()),
|
||||
'has-dark-text': colorIsDark(t.getHexColor()),
|
||||
}"
|
||||
:grid-x="dayWidth"
|
||||
:h="31"
|
||||
:is-active="canWrite"
|
||||
:minw="dayWidth"
|
||||
:parent-limitation="true"
|
||||
:parent-w="fullWidth"
|
||||
:snap-to-grid="true"
|
||||
:sticks="['mr', 'ml']"
|
||||
:style="{
|
||||
'border-color': t.getHexColor(),
|
||||
'background-color': t.getHexColor(),
|
||||
}"
|
||||
:w="t.durationDays * dayWidth"
|
||||
:x="t.offsetDays * dayWidth - 6"
|
||||
:y="0"
|
||||
axis="x"
|
||||
class="task"
|
||||
@dragstop="(e) => resizeTask(t, e)"
|
||||
@resizestop="(e) => resizeTask(t, e)"
|
||||
>
|
||||
<span
|
||||
:class="{
|
||||
'has-high-priority': t.priority >= priorities.HIGH,
|
||||
'has-not-so-high-priority':
|
||||
t.priority === priorities.HIGH,
|
||||
'has-super-high-priority':
|
||||
t.priority === priorities.DO_NOW,
|
||||
}"
|
||||
>
|
||||
{{ t.title }}
|
||||
</span>
|
||||
<priority-label
|
||||
:priority="t.priority"
|
||||
:done="t.done"
|
||||
/>
|
||||
<!-- using the key here forces vue to use the updated version model and not the response returned by the api -->
|
||||
<!-- FIXME: add label -->
|
||||
<BaseButton
|
||||
class="edit-toggle"
|
||||
@click="editTask(theTasks[k])"
|
||||
>
|
||||
<icon icon="pen" />
|
||||
</BaseButton>
|
||||
</VueDragResize>
|
||||
</div>
|
||||
<template v-if="showTaskswithoutDates">
|
||||
<div
|
||||
v-for="(t, k) in tasksWithoutDates"
|
||||
:key="t.id"
|
||||
:style="{
|
||||
background:
|
||||
'repeating-linear-gradient(90deg, #ededed, #ededed 1px, ' +
|
||||
(k % 2 === 0
|
||||
? '#fafafa 1px, #fafafa '
|
||||
: '#fff 1px, #fff ') +
|
||||
dayWidth +
|
||||
'px)',
|
||||
}"
|
||||
class="row"
|
||||
>
|
||||
<VueDragResize
|
||||
v-tooltip="$t('list.gantt.noDates')"
|
||||
:grid-x="dayWidth"
|
||||
:h="31"
|
||||
:is-active="canWrite"
|
||||
:minw="dayWidth"
|
||||
:parent-limitation="true"
|
||||
:parent-w="fullWidth"
|
||||
:snap-to-grid="true"
|
||||
:sticks="['mr', 'ml']"
|
||||
:x="dayOffsetUntilToday * dayWidth - 6"
|
||||
:y="0"
|
||||
axis="x"
|
||||
class="task nodate"
|
||||
@dragstop="(e) => resizeTask(t, e)"
|
||||
@resizestop="(e) => resizeTask(t, e)"
|
||||
>
|
||||
<span>{{ t.title }}</span>
|
||||
</VueDragResize>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<form
|
||||
v-if="canWrite"
|
||||
class="add-new-task"
|
||||
@submit.prevent="addNewTask()"
|
||||
>
|
||||
<transition name="width">
|
||||
<input
|
||||
v-if="newTaskFieldActive"
|
||||
ref="newTaskTitleField"
|
||||
v-model="newTaskTitle"
|
||||
class="input"
|
||||
type="text"
|
||||
@blur="hideCrateNewTask"
|
||||
@keyup.esc="newTaskFieldActive = false"
|
||||
>
|
||||
</transition>
|
||||
<x-button
|
||||
:shadow="false"
|
||||
icon="plus"
|
||||
@click="showCreateNewTask"
|
||||
>
|
||||
{{ $t('list.list.newTaskCta') }}
|
||||
</x-button>
|
||||
</form>
|
||||
<transition name="fade">
|
||||
<edit-task
|
||||
v-if="isTaskEdit"
|
||||
class="taskedit"
|
||||
:title="$t('list.list.editTask')"
|
||||
:task="taskToEdit"
|
||||
@close="() => {isTaskEdit = false;taskToEdit = null}"
|
||||
/>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -1,140 +1,151 @@
|
|||
<template>
|
||||
<div class="attachments">
|
||||
<h3>
|
||||
<span class="icon is-grey">
|
||||
<icon icon="paperclip"/>
|
||||
</span>
|
||||
{{ $t('task.attachment.title') }}
|
||||
</h3>
|
||||
<div class="attachments">
|
||||
<h3>
|
||||
<span class="icon is-grey">
|
||||
<icon icon="paperclip" />
|
||||
</span>
|
||||
{{ $t('task.attachment.title') }}
|
||||
</h3>
|
||||
|
||||
<input
|
||||
v-if="editEnabled"
|
||||
:disabled="attachmentService.loading || undefined"
|
||||
@change="uploadNewAttachment()"
|
||||
id="files"
|
||||
multiple
|
||||
ref="filesRef"
|
||||
type="file"
|
||||
/>
|
||||
<progress
|
||||
v-if="attachmentService.uploadProgress > 0"
|
||||
:value="attachmentService.uploadProgress"
|
||||
class="progress is-primary"
|
||||
max="100"
|
||||
>
|
||||
{{ attachmentService.uploadProgress }}%
|
||||
</progress>
|
||||
<input
|
||||
v-if="editEnabled"
|
||||
id="files"
|
||||
ref="filesRef"
|
||||
:disabled="attachmentService.loading || undefined"
|
||||
multiple
|
||||
type="file"
|
||||
@change="uploadNewAttachment()"
|
||||
>
|
||||
<progress
|
||||
v-if="attachmentService.uploadProgress > 0"
|
||||
:value="attachmentService.uploadProgress"
|
||||
class="progress is-primary"
|
||||
max="100"
|
||||
>
|
||||
{{ attachmentService.uploadProgress }}%
|
||||
</progress>
|
||||
|
||||
<div class="files" v-if="attachments.length > 0">
|
||||
<!-- FIXME: don't use a for element that wraps other links / buttons
|
||||
<div
|
||||
v-if="attachments.length > 0"
|
||||
class="files"
|
||||
>
|
||||
<!-- FIXME: don't use a for element that wraps other links / buttons
|
||||
Instead: overlay element with button that is inside.
|
||||
-->
|
||||
<a
|
||||
class="attachment"
|
||||
v-for="a in attachments"
|
||||
:key="a.id"
|
||||
@click="viewOrDownload(a)"
|
||||
>
|
||||
<div class="filename">{{ a.file.name }}</div>
|
||||
<div class="info">
|
||||
<p class="attachment-info-meta">
|
||||
<i18n-t keypath="task.attachment.createdBy" scope="global">
|
||||
<span v-tooltip="formatDateLong(a.created)">
|
||||
{{ formatDateSince(a.created) }}
|
||||
</span>
|
||||
<User
|
||||
:avatar-size="24"
|
||||
:user="a.createdBy"
|
||||
:is-inline="true"
|
||||
/>
|
||||
</i18n-t>
|
||||
<span>
|
||||
{{ getHumanSize(a.file.size) }}
|
||||
</span>
|
||||
<span v-if="a.file.mime">
|
||||
{{ a.file.mime }}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<BaseButton
|
||||
class="attachment-info-meta-button"
|
||||
@click.prevent.stop="downloadAttachment(a)"
|
||||
v-tooltip="$t('task.attachment.downloadTooltip')"
|
||||
>
|
||||
{{ $t('misc.download') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
class="attachment-info-meta-button"
|
||||
@click.stop="copyUrl(a)"
|
||||
v-tooltip="$t('task.attachment.copyUrlTooltip')"
|
||||
>
|
||||
{{ $t('task.attachment.copyUrl') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="editEnabled"
|
||||
class="attachment-info-meta-button"
|
||||
@click.prevent.stop="setAttachmentToDelete(a)"
|
||||
v-tooltip="$t('task.attachment.deleteTooltip')"
|
||||
>
|
||||
{{ $t('misc.delete') }}
|
||||
</BaseButton>
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
v-for="a in attachments"
|
||||
:key="a.id"
|
||||
class="attachment"
|
||||
@click="viewOrDownload(a)"
|
||||
>
|
||||
<div class="filename">{{ a.file.name }}</div>
|
||||
<div class="info">
|
||||
<p class="attachment-info-meta">
|
||||
<i18n-t
|
||||
keypath="task.attachment.createdBy"
|
||||
scope="global"
|
||||
>
|
||||
<span v-tooltip="formatDateLong(a.created)">
|
||||
{{ formatDateSince(a.created) }}
|
||||
</span>
|
||||
<User
|
||||
:avatar-size="24"
|
||||
:user="a.createdBy"
|
||||
:is-inline="true"
|
||||
/>
|
||||
</i18n-t>
|
||||
<span>
|
||||
{{ getHumanSize(a.file.size) }}
|
||||
</span>
|
||||
<span v-if="a.file.mime">
|
||||
{{ a.file.mime }}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<BaseButton
|
||||
v-tooltip="$t('task.attachment.downloadTooltip')"
|
||||
class="attachment-info-meta-button"
|
||||
@click.prevent.stop="downloadAttachment(a)"
|
||||
>
|
||||
{{ $t('misc.download') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-tooltip="$t('task.attachment.copyUrlTooltip')"
|
||||
class="attachment-info-meta-button"
|
||||
@click.stop="copyUrl(a)"
|
||||
>
|
||||
{{ $t('task.attachment.copyUrl') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="editEnabled"
|
||||
v-tooltip="$t('task.attachment.deleteTooltip')"
|
||||
class="attachment-info-meta-button"
|
||||
@click.prevent.stop="setAttachmentToDelete(a)"
|
||||
>
|
||||
{{ $t('misc.delete') }}
|
||||
</BaseButton>
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<x-button
|
||||
v-if="editEnabled"
|
||||
:disabled="attachmentService.loading"
|
||||
@click="filesRef?.click()"
|
||||
class="mb-4"
|
||||
icon="cloud-upload-alt"
|
||||
variant="secondary"
|
||||
:shadow="false"
|
||||
>
|
||||
{{ $t('task.attachment.upload') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
v-if="editEnabled"
|
||||
:disabled="attachmentService.loading"
|
||||
class="mb-4"
|
||||
icon="cloud-upload-alt"
|
||||
variant="secondary"
|
||||
:shadow="false"
|
||||
@click="filesRef?.click()"
|
||||
>
|
||||
{{ $t('task.attachment.upload') }}
|
||||
</x-button>
|
||||
|
||||
<!-- Dropzone -->
|
||||
<div
|
||||
:class="{ hidden: !isOverDropZone }"
|
||||
class="dropzone"
|
||||
v-if="editEnabled"
|
||||
>
|
||||
<div class="drop-hint">
|
||||
<div class="icon">
|
||||
<icon icon="cloud-upload-alt"/>
|
||||
</div>
|
||||
<div class="hint">{{ $t('task.attachment.drop') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Dropzone -->
|
||||
<div
|
||||
v-if="editEnabled"
|
||||
:class="{ hidden: !isOverDropZone }"
|
||||
class="dropzone"
|
||||
>
|
||||
<div class="drop-hint">
|
||||
<div class="icon">
|
||||
<icon icon="cloud-upload-alt" />
|
||||
</div>
|
||||
<div class="hint">
|
||||
{{ $t('task.attachment.drop') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete modal -->
|
||||
<modal
|
||||
v-if="attachmentToDelete !== null"
|
||||
@close="setAttachmentToDelete(null)"
|
||||
@submit="deleteAttachment()"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{ $t('task.attachment.delete') }}</span>
|
||||
</template>
|
||||
<!-- Delete modal -->
|
||||
<modal
|
||||
v-if="attachmentToDelete !== null"
|
||||
@close="setAttachmentToDelete(null)"
|
||||
@submit="deleteAttachment()"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{ $t('task.attachment.delete') }}</span>
|
||||
</template>
|
||||
|
||||
<template #text>
|
||||
<p>
|
||||
{{ $t('task.attachment.deleteText1', {filename: attachmentToDelete.file.name}) }}<br/>
|
||||
<strong class="has-text-white">{{ $t('misc.cannotBeUndone') }}</strong>
|
||||
</p>
|
||||
</template>
|
||||
</modal>
|
||||
<template #text>
|
||||
<p>
|
||||
{{ $t('task.attachment.deleteText1', {filename: attachmentToDelete.file.name}) }}<br>
|
||||
<strong class="has-text-white">{{ $t('misc.cannotBeUndone') }}</strong>
|
||||
</p>
|
||||
</template>
|
||||
</modal>
|
||||
|
||||
<!-- Attachment image modal -->
|
||||
<modal
|
||||
v-if="attachmentImageBlobUrl !== null"
|
||||
@close="attachmentImageBlobUrl = null"
|
||||
>
|
||||
<img :src="attachmentImageBlobUrl" alt=""/>
|
||||
</modal>
|
||||
</div>
|
||||
<!-- Attachment image modal -->
|
||||
<modal
|
||||
v-if="attachmentImageBlobUrl !== null"
|
||||
@close="attachmentImageBlobUrl = null"
|
||||
>
|
||||
<img
|
||||
:src="attachmentImageBlobUrl"
|
||||
alt=""
|
||||
>
|
||||
</modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,12 +1,32 @@
|
|||
<template>
|
||||
<span v-if="checklist.total > 0" class="checklist-summary">
|
||||
<svg width="12" height="12">
|
||||
<circle stroke-width="2" fill="transparent" cx="50%" cy="50%" r="5"></circle>
|
||||
<circle stroke-width="2" stroke-dasharray="31" :stroke-dashoffset="checklistCircleDone"
|
||||
stroke-linecap="round" fill="transparent" cx="50%" cy="50%" r="5"></circle>
|
||||
</svg>
|
||||
<span>{{ label }}</span>
|
||||
</span>
|
||||
<span
|
||||
v-if="checklist.total > 0"
|
||||
class="checklist-summary"
|
||||
>
|
||||
<svg
|
||||
width="12"
|
||||
height="12"
|
||||
>
|
||||
<circle
|
||||
stroke-width="2"
|
||||
fill="transparent"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
r="5"
|
||||
/>
|
||||
<circle
|
||||
stroke-width="2"
|
||||
stroke-dasharray="31"
|
||||
:stroke-dashoffset="checklistCircleDone"
|
||||
stroke-linecap="round"
|
||||
fill="transparent"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
r="5"
|
||||
/>
|
||||
</svg>
|
||||
<span>{{ label }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,154 +1,172 @@
|
|||
<template>
|
||||
<div class="content details" v-if="enabled">
|
||||
<h3 v-if="canWrite || comments.length > 0" :class="{'d-print-none': comments.length === 0}">
|
||||
<span class="icon is-grey">
|
||||
<icon :icon="['far', 'comments']"/>
|
||||
</span>
|
||||
{{ $t('task.comment.title') }}
|
||||
</h3>
|
||||
<div class="comments">
|
||||
<span
|
||||
class="is-inline-flex is-align-items-center"
|
||||
v-if="taskCommentService.loading && saving === null && !creating"
|
||||
>
|
||||
<span class="loader is-inline-block mr-2"></span>
|
||||
{{ $t('task.comment.loading') }}
|
||||
</span>
|
||||
<div :key="c.id" class="media comment" v-for="c in comments">
|
||||
<figure class="media-left is-hidden-mobile">
|
||||
<img
|
||||
:src="getAvatarUrl(c.author, 48)"
|
||||
alt=""
|
||||
class="image is-avatar"
|
||||
height="48"
|
||||
width="48"
|
||||
/>
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<div class="comment-info">
|
||||
<img
|
||||
:src="getAvatarUrl(c.author, 20)"
|
||||
alt=""
|
||||
class="image is-avatar d-print-none"
|
||||
height="20"
|
||||
width="20"
|
||||
/>
|
||||
<strong>{{ getDisplayName(c.author) }}</strong>
|
||||
<span v-tooltip="formatDateLong(c.created)" class="has-text-grey">
|
||||
{{ formatDateSince(c.created) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="+new Date(c.created) !== +new Date(c.updated)"
|
||||
v-tooltip="formatDateLong(c.updated)"
|
||||
>
|
||||
· {{ $t('task.comment.edited', {date: formatDateSince(c.updated)}) }}
|
||||
</span>
|
||||
<transition name="fade">
|
||||
<span
|
||||
class="is-inline-flex"
|
||||
v-if="
|
||||
taskCommentService.loading &&
|
||||
saving === c.id
|
||||
"
|
||||
>
|
||||
<span class="loader is-inline-block mr-2"></span>
|
||||
{{ $t('misc.saving') }}
|
||||
</span>
|
||||
<span
|
||||
class="has-text-success"
|
||||
v-else-if="
|
||||
!taskCommentService.loading &&
|
||||
saved === c.id
|
||||
"
|
||||
>
|
||||
{{ $t('misc.saved') }}
|
||||
</span>
|
||||
</transition>
|
||||
</div>
|
||||
<editor
|
||||
:hasPreview="true"
|
||||
:is-edit-enabled="canWrite && c.author.id === currentUserId"
|
||||
:upload-callback="attachmentUpload"
|
||||
:upload-enabled="true"
|
||||
v-model="c.comment"
|
||||
@update:model-value="
|
||||
() => {
|
||||
toggleEdit(c)
|
||||
editComment()
|
||||
}
|
||||
"
|
||||
:bottom-actions="actions[c.id]"
|
||||
:show-save="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="media comment d-print-none" v-if="canWrite">
|
||||
<figure class="media-left is-hidden-mobile">
|
||||
<img
|
||||
:src="userAvatar"
|
||||
alt=""
|
||||
class="image is-avatar"
|
||||
height="48"
|
||||
width="48"
|
||||
/>
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<div class="form">
|
||||
<transition name="fade">
|
||||
<span
|
||||
class="is-inline-flex"
|
||||
v-if="taskCommentService.loading && creating"
|
||||
>
|
||||
<span class="loader is-inline-block mr-2"></span>
|
||||
{{ $t('task.comment.creating') }}
|
||||
</span>
|
||||
</transition>
|
||||
<div class="field">
|
||||
<editor
|
||||
:class="{
|
||||
'is-loading':
|
||||
taskCommentService.loading &&
|
||||
!isCommentEdit,
|
||||
}"
|
||||
:hasPreview="false"
|
||||
:upload-callback="attachmentUpload"
|
||||
:upload-enabled="true"
|
||||
:placeholder="$t('task.comment.placeholder')"
|
||||
v-if="editorActive"
|
||||
v-model="newComment.comment"
|
||||
/>
|
||||
</div>
|
||||
<div class="field">
|
||||
<x-button
|
||||
:loading="taskCommentService.loading && !isCommentEdit"
|
||||
:disabled="newComment.comment === ''"
|
||||
@click="addComment()"
|
||||
>
|
||||
{{ $t('task.comment.comment') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="enabled"
|
||||
class="content details"
|
||||
>
|
||||
<h3
|
||||
v-if="canWrite || comments.length > 0"
|
||||
:class="{'d-print-none': comments.length === 0}"
|
||||
>
|
||||
<span class="icon is-grey">
|
||||
<icon :icon="['far', 'comments']" />
|
||||
</span>
|
||||
{{ $t('task.comment.title') }}
|
||||
</h3>
|
||||
<div class="comments">
|
||||
<span
|
||||
v-if="taskCommentService.loading && saving === null && !creating"
|
||||
class="is-inline-flex is-align-items-center"
|
||||
>
|
||||
<span class="loader is-inline-block mr-2" />
|
||||
{{ $t('task.comment.loading') }}
|
||||
</span>
|
||||
<div
|
||||
v-for="c in comments"
|
||||
:key="c.id"
|
||||
class="media comment"
|
||||
>
|
||||
<figure class="media-left is-hidden-mobile">
|
||||
<img
|
||||
:src="getAvatarUrl(c.author, 48)"
|
||||
alt=""
|
||||
class="image is-avatar"
|
||||
height="48"
|
||||
width="48"
|
||||
>
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<div class="comment-info">
|
||||
<img
|
||||
:src="getAvatarUrl(c.author, 20)"
|
||||
alt=""
|
||||
class="image is-avatar d-print-none"
|
||||
height="20"
|
||||
width="20"
|
||||
>
|
||||
<strong>{{ getDisplayName(c.author) }}</strong>
|
||||
<span
|
||||
v-tooltip="formatDateLong(c.created)"
|
||||
class="has-text-grey"
|
||||
>
|
||||
{{ formatDateSince(c.created) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="+new Date(c.created) !== +new Date(c.updated)"
|
||||
v-tooltip="formatDateLong(c.updated)"
|
||||
>
|
||||
· {{ $t('task.comment.edited', {date: formatDateSince(c.updated)}) }}
|
||||
</span>
|
||||
<transition name="fade">
|
||||
<span
|
||||
v-if="
|
||||
taskCommentService.loading &&
|
||||
saving === c.id
|
||||
"
|
||||
class="is-inline-flex"
|
||||
>
|
||||
<span class="loader is-inline-block mr-2" />
|
||||
{{ $t('misc.saving') }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="
|
||||
!taskCommentService.loading &&
|
||||
saved === c.id
|
||||
"
|
||||
class="has-text-success"
|
||||
>
|
||||
{{ $t('misc.saved') }}
|
||||
</span>
|
||||
</transition>
|
||||
</div>
|
||||
<editor
|
||||
v-model="c.comment"
|
||||
:has-preview="true"
|
||||
:is-edit-enabled="canWrite && c.author.id === currentUserId"
|
||||
:upload-callback="attachmentUpload"
|
||||
:upload-enabled="true"
|
||||
:bottom-actions="actions[c.id]"
|
||||
:show-save="true"
|
||||
@update:model-value="
|
||||
() => {
|
||||
toggleEdit(c)
|
||||
editComment()
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="canWrite"
|
||||
class="media comment d-print-none"
|
||||
>
|
||||
<figure class="media-left is-hidden-mobile">
|
||||
<img
|
||||
:src="userAvatar"
|
||||
alt=""
|
||||
class="image is-avatar"
|
||||
height="48"
|
||||
width="48"
|
||||
>
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<div class="form">
|
||||
<transition name="fade">
|
||||
<span
|
||||
v-if="taskCommentService.loading && creating"
|
||||
class="is-inline-flex"
|
||||
>
|
||||
<span class="loader is-inline-block mr-2" />
|
||||
{{ $t('task.comment.creating') }}
|
||||
</span>
|
||||
</transition>
|
||||
<div class="field">
|
||||
<editor
|
||||
v-if="editorActive"
|
||||
v-model="newComment.comment"
|
||||
:class="{
|
||||
'is-loading':
|
||||
taskCommentService.loading &&
|
||||
!isCommentEdit,
|
||||
}"
|
||||
:has-preview="false"
|
||||
:upload-callback="attachmentUpload"
|
||||
:upload-enabled="true"
|
||||
:placeholder="$t('task.comment.placeholder')"
|
||||
/>
|
||||
</div>
|
||||
<div class="field">
|
||||
<x-button
|
||||
:loading="taskCommentService.loading && !isCommentEdit"
|
||||
:disabled="newComment.comment === ''"
|
||||
@click="addComment()"
|
||||
>
|
||||
{{ $t('task.comment.comment') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<transition name="modal">
|
||||
<modal
|
||||
v-if="showDeleteModal"
|
||||
@close="showDeleteModal = false"
|
||||
@submit="() => deleteComment(commentToDelete)"
|
||||
>
|
||||
<template #header><span>{{ $t('task.comment.delete') }}</span></template>
|
||||
<transition name="modal">
|
||||
<modal
|
||||
v-if="showDeleteModal"
|
||||
@close="showDeleteModal = false"
|
||||
@submit="() => deleteComment(commentToDelete)"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{ $t('task.comment.delete') }}</span>
|
||||
</template>
|
||||
|
||||
<template #text>
|
||||
<p>
|
||||
{{ $t('task.comment.deleteText1') }}<br/>
|
||||
<strong class="has-text-white">{{ $t('misc.cannotBeUndone') }}</strong>
|
||||
</p>
|
||||
</template>
|
||||
</modal>
|
||||
</transition>
|
||||
</div>
|
||||
<template #text>
|
||||
<p>
|
||||
{{ $t('task.comment.deleteText1') }}<br>
|
||||
<strong class="has-text-white">{{ $t('misc.cannotBeUndone') }}</strong>
|
||||
</p>
|
||||
</template>
|
||||
</modal>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,29 +1,47 @@
|
|||
<template>
|
||||
<p class="created">
|
||||
<time :datetime="formatISO(task.created)" v-tooltip="formatDateLong(task.created)">
|
||||
<i18n-t keypath="task.detail.created" scope="global">
|
||||
<span>{{ formatDateSince(task.created) }}</span>
|
||||
{{ getDisplayName(task.createdBy) }}
|
||||
</i18n-t>
|
||||
</time>
|
||||
<template v-if="+new Date(task.created) !== +new Date(task.updated)">
|
||||
<br/>
|
||||
<!-- Computed properties to show the actual date every time it gets updated -->
|
||||
<time :datetime="formatISO(task.updated)" v-tooltip="updatedFormatted">
|
||||
<i18n-t keypath="task.detail.updated" scope="global">
|
||||
<span>{{ updatedSince }}</span>
|
||||
</i18n-t>
|
||||
</time>
|
||||
</template>
|
||||
<template v-if="task.done">
|
||||
<br/>
|
||||
<time :datetime="formatISO(task.doneAt)" v-tooltip="doneFormatted">
|
||||
<i18n-t keypath="task.detail.doneAt" scope="global">
|
||||
<span>{{ doneSince }}</span>
|
||||
</i18n-t>
|
||||
</time>
|
||||
</template>
|
||||
</p>
|
||||
<p class="created">
|
||||
<time
|
||||
v-tooltip="formatDateLong(task.created)"
|
||||
:datetime="formatISO(task.created)"
|
||||
>
|
||||
<i18n-t
|
||||
keypath="task.detail.created"
|
||||
scope="global"
|
||||
>
|
||||
<span>{{ formatDateSince(task.created) }}</span>
|
||||
{{ getDisplayName(task.createdBy) }}
|
||||
</i18n-t>
|
||||
</time>
|
||||
<template v-if="+new Date(task.created) !== +new Date(task.updated)">
|
||||
<br>
|
||||
<!-- Computed properties to show the actual date every time it gets updated -->
|
||||
<time
|
||||
v-tooltip="updatedFormatted"
|
||||
:datetime="formatISO(task.updated)"
|
||||
>
|
||||
<i18n-t
|
||||
keypath="task.detail.updated"
|
||||
scope="global"
|
||||
>
|
||||
<span>{{ updatedSince }}</span>
|
||||
</i18n-t>
|
||||
</time>
|
||||
</template>
|
||||
<template v-if="task.done">
|
||||
<br>
|
||||
<time
|
||||
v-tooltip="doneFormatted"
|
||||
:datetime="formatISO(task.doneAt)"
|
||||
>
|
||||
<i18n-t
|
||||
keypath="task.detail.doneAt"
|
||||
scope="global"
|
||||
>
|
||||
<span>{{ doneSince }}</span>
|
||||
</i18n-t>
|
||||
</time>
|
||||
</template>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<td v-tooltip="+date === 0 ? '' : formatDateLong(date)">
|
||||
<time :datetime="date ? formatISO(date) : undefined">
|
||||
{{ +date === 0 ? '-' : formatDateSince(date) }}
|
||||
</time>
|
||||
</td>
|
||||
<td v-tooltip="+date === 0 ? '' : formatDateLong(date)">
|
||||
<time :datetime="date ? formatISO(date) : undefined">
|
||||
{{ +date === 0 ? '-' : formatDateSince(date) }}
|
||||
</time>
|
||||
</td>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{ 'is-loading': taskService.loading }"
|
||||
class="defer-task loading-container"
|
||||
>
|
||||
<label class="label">{{ $t('task.deferDueDate.title') }}</label>
|
||||
<div class="defer-days">
|
||||
<x-button
|
||||
@click.prevent.stop="() => deferDays(1)"
|
||||
:shadow="false"
|
||||
variant="secondary"
|
||||
>
|
||||
{{ $t('task.deferDueDate.1day') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
@click.prevent.stop="() => deferDays(3)"
|
||||
:shadow="false"
|
||||
variant="secondary"
|
||||
>
|
||||
{{ $t('task.deferDueDate.3days') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
@click.prevent.stop="() => deferDays(7)"
|
||||
:shadow="false"
|
||||
variant="secondary"
|
||||
>
|
||||
{{ $t('task.deferDueDate.1week') }}
|
||||
</x-button>
|
||||
</div>
|
||||
<flat-pickr
|
||||
:class="{ disabled: taskService.loading }"
|
||||
:config="flatPickerConfig"
|
||||
:disabled="taskService.loading || undefined"
|
||||
class="input"
|
||||
v-model="dueDate"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
:class="{ 'is-loading': taskService.loading }"
|
||||
class="defer-task loading-container"
|
||||
>
|
||||
<label class="label">{{ $t('task.deferDueDate.title') }}</label>
|
||||
<div class="defer-days">
|
||||
<x-button
|
||||
:shadow="false"
|
||||
variant="secondary"
|
||||
@click.prevent.stop="() => deferDays(1)"
|
||||
>
|
||||
{{ $t('task.deferDueDate.1day') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
:shadow="false"
|
||||
variant="secondary"
|
||||
@click.prevent.stop="() => deferDays(3)"
|
||||
>
|
||||
{{ $t('task.deferDueDate.3days') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
:shadow="false"
|
||||
variant="secondary"
|
||||
@click.prevent.stop="() => deferDays(7)"
|
||||
>
|
||||
{{ $t('task.deferDueDate.1week') }}
|
||||
</x-button>
|
||||
</div>
|
||||
<flat-pickr
|
||||
v-model="dueDate"
|
||||
:class="{ disabled: taskService.loading }"
|
||||
:config="flatPickerConfig"
|
||||
:disabled="taskService.loading || undefined"
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,33 +1,39 @@
|
|||
<template>
|
||||
<div>
|
||||
<h3>
|
||||
<span class="icon is-grey">
|
||||
<icon icon="align-left"/>
|
||||
</span>
|
||||
{{ $t('task.attributes.description') }}
|
||||
<transition name="fade">
|
||||
<span class="is-small is-inline-flex" v-if="loading && saving">
|
||||
<span class="loader is-inline-block mr-2"></span>
|
||||
{{ $t('misc.saving') }}
|
||||
</span>
|
||||
<span class="is-small has-text-success" v-else-if="!loading && saved">
|
||||
<icon icon="check"/>
|
||||
{{ $t('misc.saved') }}
|
||||
</span>
|
||||
</transition>
|
||||
</h3>
|
||||
<editor
|
||||
:is-edit-enabled="canWrite"
|
||||
:upload-callback="attachmentUpload"
|
||||
:upload-enabled="true"
|
||||
:placeholder="$t('task.description.placeholder')"
|
||||
:empty-text="$t('task.description.empty')"
|
||||
:show-save="true"
|
||||
edit-shortcut="e"
|
||||
v-model="task.description"
|
||||
@update:model-value="save"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3>
|
||||
<span class="icon is-grey">
|
||||
<icon icon="align-left" />
|
||||
</span>
|
||||
{{ $t('task.attributes.description') }}
|
||||
<transition name="fade">
|
||||
<span
|
||||
v-if="loading && saving"
|
||||
class="is-small is-inline-flex"
|
||||
>
|
||||
<span class="loader is-inline-block mr-2" />
|
||||
{{ $t('misc.saving') }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="!loading && saved"
|
||||
class="is-small has-text-success"
|
||||
>
|
||||
<icon icon="check" />
|
||||
{{ $t('misc.saved') }}
|
||||
</span>
|
||||
</transition>
|
||||
</h3>
|
||||
<editor
|
||||
v-model="task.description"
|
||||
:is-edit-enabled="canWrite"
|
||||
:upload-callback="attachmentUpload"
|
||||
:upload-enabled="true"
|
||||
:placeholder="$t('task.description.placeholder')"
|
||||
:empty-text="$t('task.description.empty')"
|
||||
:show-save="true"
|
||||
edit-shortcut="e"
|
||||
@update:model-value="save"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,30 +1,38 @@
|
|||
<template>
|
||||
<div
|
||||
tabindex="-1"
|
||||
@focus="focus"
|
||||
>
|
||||
<Multiselect
|
||||
:loading="listUserService.loading"
|
||||
:placeholder="$t('task.assignee.placeholder')"
|
||||
:multiple="true"
|
||||
@search="findUser"
|
||||
:search-results="foundUsers"
|
||||
@select="addAssignee"
|
||||
label="name"
|
||||
:select-placeholder="$t('task.assignee.selectPlaceholder')"
|
||||
v-model="assignees"
|
||||
ref="multiselect"
|
||||
>
|
||||
<template #tag="{item: user}">
|
||||
<span class="assignee">
|
||||
<user :avatar-size="32" :show-username="false" :user="user"/>
|
||||
<BaseButton @click="removeAssignee(user)" class="remove-assignee" v-if="!disabled">
|
||||
<icon icon="times"/>
|
||||
</BaseButton>
|
||||
</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
</div>
|
||||
<div
|
||||
tabindex="-1"
|
||||
@focus="focus"
|
||||
>
|
||||
<Multiselect
|
||||
ref="multiselect"
|
||||
v-model="assignees"
|
||||
:loading="listUserService.loading"
|
||||
:placeholder="$t('task.assignee.placeholder')"
|
||||
:multiple="true"
|
||||
:search-results="foundUsers"
|
||||
label="name"
|
||||
:select-placeholder="$t('task.assignee.selectPlaceholder')"
|
||||
@search="findUser"
|
||||
@select="addAssignee"
|
||||
>
|
||||
<template #tag="{item: user}">
|
||||
<span class="assignee">
|
||||
<user
|
||||
:avatar-size="32"
|
||||
:show-username="false"
|
||||
:user="user"
|
||||
/>
|
||||
<BaseButton
|
||||
v-if="!disabled"
|
||||
class="remove-assignee"
|
||||
@click="removeAssignee(user)"
|
||||
>
|
||||
<icon icon="times" />
|
||||
</BaseButton>
|
||||
</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,41 +1,48 @@
|
|||
<template>
|
||||
<Multiselect
|
||||
:loading="loading"
|
||||
:placeholder="$t('task.label.placeholder')"
|
||||
:multiple="true"
|
||||
@search="findLabel"
|
||||
:search-results="foundLabels"
|
||||
@select="addLabel"
|
||||
label="title"
|
||||
:creatable="true"
|
||||
@create="createAndAddLabel"
|
||||
:create-placeholder="$t('task.label.createPlaceholder')"
|
||||
v-model="labels"
|
||||
:search-delay="10"
|
||||
:close-after-select="false"
|
||||
>
|
||||
<template #tag="{item: label}">
|
||||
<span
|
||||
:style="{'background': label.hexColor, 'color': label.textColor}"
|
||||
class="tag">
|
||||
<span>{{ label.title }}</span>
|
||||
<BaseButton v-cy="'taskDetail.removeLabel'" @click="removeLabel(label)" class="delete is-small" />
|
||||
</span>
|
||||
</template>
|
||||
<template #searchResult="{option}">
|
||||
<span
|
||||
v-if="typeof option === 'string'"
|
||||
class="tag search-result">
|
||||
<span>{{ option }}</span>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
:style="{'background': option.hexColor, 'color': option.textColor}"
|
||||
class="tag search-result">
|
||||
<span>{{ option.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
<Multiselect
|
||||
v-model="labels"
|
||||
:loading="loading"
|
||||
:placeholder="$t('task.label.placeholder')"
|
||||
:multiple="true"
|
||||
:search-results="foundLabels"
|
||||
label="title"
|
||||
:creatable="true"
|
||||
:create-placeholder="$t('task.label.createPlaceholder')"
|
||||
:search-delay="10"
|
||||
:close-after-select="false"
|
||||
@search="findLabel"
|
||||
@select="addLabel"
|
||||
@create="createAndAddLabel"
|
||||
>
|
||||
<template #tag="{item: label}">
|
||||
<span
|
||||
:style="{'background': label.hexColor, 'color': label.textColor}"
|
||||
class="tag"
|
||||
>
|
||||
<span>{{ label.title }}</span>
|
||||
<BaseButton
|
||||
v-cy="'taskDetail.removeLabel'"
|
||||
class="delete is-small"
|
||||
@click="removeLabel(label)"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
<template #searchResult="{option}">
|
||||
<span
|
||||
v-if="typeof option === 'string'"
|
||||
class="tag search-result"
|
||||
>
|
||||
<span>{{ option }}</span>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
:style="{'background': option.hexColor, 'color': option.textColor}"
|
||||
class="tag search-result"
|
||||
>
|
||||
<span>{{ option.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,39 +1,49 @@
|
|||
<template>
|
||||
<div class="heading">
|
||||
<BaseButton @click="copyUrl"><h1 class="title task-id">{{ textIdentifier }}</h1></BaseButton>
|
||||
<Done class="heading__done" :is-done="task.done"/>
|
||||
<ColorBubble
|
||||
v-if="task.hexColor !== ''"
|
||||
:color="task.getHexColor()"
|
||||
class="mt-1 ml-2"
|
||||
/>
|
||||
<h1
|
||||
class="title input"
|
||||
:class="{'disabled': !canWrite}"
|
||||
@blur="save(($event.target as HTMLInputElement).textContent as string)"
|
||||
@keydown.enter.prevent.stop="($event.target as HTMLInputElement).blur()"
|
||||
:contenteditable="canWrite ? true : undefined"
|
||||
:spellcheck="false"
|
||||
>
|
||||
{{ task.title.trim() }}
|
||||
</h1>
|
||||
<transition name="fade">
|
||||
<span
|
||||
v-if="loading && saving"
|
||||
class="is-inline-flex is-align-items-center"
|
||||
>
|
||||
<span class="loader is-inline-block mr-2"></span>
|
||||
{{ $t('misc.saving') }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="!loading && showSavedMessage"
|
||||
class="has-text-success is-inline-flex is-align-content-center"
|
||||
>
|
||||
<icon icon="check" class="mr-2"/>
|
||||
{{ $t('misc.saved') }}
|
||||
</span>
|
||||
</transition>
|
||||
</div>
|
||||
<div class="heading">
|
||||
<BaseButton @click="copyUrl">
|
||||
<h1 class="title task-id">
|
||||
{{ textIdentifier }}
|
||||
</h1>
|
||||
</BaseButton>
|
||||
<Done
|
||||
class="heading__done"
|
||||
:is-done="task.done"
|
||||
/>
|
||||
<ColorBubble
|
||||
v-if="task.hexColor !== ''"
|
||||
:color="task.getHexColor()"
|
||||
class="mt-1 ml-2"
|
||||
/>
|
||||
<h1
|
||||
class="title input"
|
||||
:class="{'disabled': !canWrite}"
|
||||
:contenteditable="canWrite ? true : undefined"
|
||||
:spellcheck="false"
|
||||
@blur="save(($event.target as HTMLInputElement).textContent as string)"
|
||||
@keydown.enter.prevent.stop="($event.target as HTMLInputElement).blur()"
|
||||
>
|
||||
{{ task.title.trim() }}
|
||||
</h1>
|
||||
<transition name="fade">
|
||||
<span
|
||||
v-if="loading && saving"
|
||||
class="is-inline-flex is-align-items-center"
|
||||
>
|
||||
<span class="loader is-inline-block mr-2" />
|
||||
{{ $t('misc.saving') }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="!loading && showSavedMessage"
|
||||
class="has-text-success is-inline-flex is-align-content-center"
|
||||
>
|
||||
<icon
|
||||
icon="check"
|
||||
class="mr-2"
|
||||
/>
|
||||
{{ $t('misc.saved') }}
|
||||
</span>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,68 +1,90 @@
|
|||
<template>
|
||||
<div
|
||||
class="task loader-container draggable"
|
||||
:class="{
|
||||
'is-loading': loadingInternal || loading,
|
||||
'draggable': !(loadingInternal || loading),
|
||||
'has-light-text': color !== TASK_DEFAULT_COLOR && !colorIsDark(color),
|
||||
}"
|
||||
:style="{'background-color': color !== TASK_DEFAULT_COLOR ? color : undefined}"
|
||||
@click.exact="openTaskDetail()"
|
||||
@click.ctrl="() => toggleTaskDone(task)"
|
||||
@click.meta="() => toggleTaskDone(task)"
|
||||
>
|
||||
<span class="task-id">
|
||||
<Done class="kanban-card__done" :is-done="task.done" variant="small"/>
|
||||
<template v-if="task.identifier === ''">
|
||||
#{{ task.index }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ task.identifier }}
|
||||
</template>
|
||||
</span>
|
||||
<span
|
||||
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
||||
class="due-date"
|
||||
v-if="task.dueDate > 0"
|
||||
v-tooltip="formatDateLong(task.dueDate)">
|
||||
<span class="icon">
|
||||
<icon :icon="['far', 'calendar-alt']"/>
|
||||
</span>
|
||||
<time :datetime="formatISO(task.dueDate)">
|
||||
{{ formatDateSince(task.dueDate) }}
|
||||
</time>
|
||||
</span>
|
||||
<h3>{{ task.title }}</h3>
|
||||
<progress
|
||||
class="progress is-small"
|
||||
v-if="task.percentDone > 0"
|
||||
:value="task.percentDone * 100" max="100">
|
||||
{{ task.percentDone * 100 }}%
|
||||
</progress>
|
||||
<div class="footer">
|
||||
<labels :labels="task.labels"/>
|
||||
<priority-label :priority="task.priority" :done="task.done"/>
|
||||
<div class="assignees" v-if="task.assignees.length > 0">
|
||||
<user
|
||||
v-for="u in task.assignees"
|
||||
:avatar-size="24"
|
||||
:key="task.id + 'assignee' + u.id"
|
||||
:show-username="false"
|
||||
:user="u"
|
||||
/>
|
||||
</div>
|
||||
<checklist-summary :task="task"/>
|
||||
<span class="icon" v-if="task.attachments.length > 0">
|
||||
<icon icon="paperclip"/>
|
||||
</span>
|
||||
<span v-if="task.description" class="icon">
|
||||
<icon icon="align-left"/>
|
||||
</span>
|
||||
<span class="icon" v-if="task.repeatAfter.amount > 0">
|
||||
<icon icon="history"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="task loader-container draggable"
|
||||
:class="{
|
||||
'is-loading': loadingInternal || loading,
|
||||
'draggable': !(loadingInternal || loading),
|
||||
'has-light-text': color !== TASK_DEFAULT_COLOR && !colorIsDark(color),
|
||||
}"
|
||||
:style="{'background-color': color !== TASK_DEFAULT_COLOR ? color : undefined}"
|
||||
@click.exact="openTaskDetail()"
|
||||
@click.ctrl="() => toggleTaskDone(task)"
|
||||
@click.meta="() => toggleTaskDone(task)"
|
||||
>
|
||||
<span class="task-id">
|
||||
<Done
|
||||
class="kanban-card__done"
|
||||
:is-done="task.done"
|
||||
variant="small"
|
||||
/>
|
||||
<template v-if="task.identifier === ''">
|
||||
#{{ task.index }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ task.identifier }}
|
||||
</template>
|
||||
</span>
|
||||
<span
|
||||
v-if="task.dueDate > 0"
|
||||
v-tooltip="formatDateLong(task.dueDate)"
|
||||
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
||||
class="due-date"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon :icon="['far', 'calendar-alt']" />
|
||||
</span>
|
||||
<time :datetime="formatISO(task.dueDate)">
|
||||
{{ formatDateSince(task.dueDate) }}
|
||||
</time>
|
||||
</span>
|
||||
<h3>{{ task.title }}</h3>
|
||||
<progress
|
||||
v-if="task.percentDone > 0"
|
||||
class="progress is-small"
|
||||
:value="task.percentDone * 100"
|
||||
max="100"
|
||||
>
|
||||
{{ task.percentDone * 100 }}%
|
||||
</progress>
|
||||
<div class="footer">
|
||||
<labels :labels="task.labels" />
|
||||
<priority-label
|
||||
:priority="task.priority"
|
||||
:done="task.done"
|
||||
/>
|
||||
<div
|
||||
v-if="task.assignees.length > 0"
|
||||
class="assignees"
|
||||
>
|
||||
<user
|
||||
v-for="u in task.assignees"
|
||||
:key="task.id + 'assignee' + u.id"
|
||||
:avatar-size="24"
|
||||
:show-username="false"
|
||||
:user="u"
|
||||
/>
|
||||
</div>
|
||||
<checklist-summary :task="task" />
|
||||
<span
|
||||
v-if="task.attachments.length > 0"
|
||||
class="icon"
|
||||
>
|
||||
<icon icon="paperclip" />
|
||||
</span>
|
||||
<span
|
||||
v-if="task.description"
|
||||
class="icon"
|
||||
>
|
||||
<icon icon="align-left" />
|
||||
</span>
|
||||
<span
|
||||
v-if="task.repeatAfter.amount > 0"
|
||||
class="icon"
|
||||
>
|
||||
<icon icon="history" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<template>
|
||||
<div class="label-wrapper">
|
||||
<span
|
||||
:key="label.id"
|
||||
:style="{'background': label.hexColor, 'color': label.textColor}"
|
||||
class="tag"
|
||||
v-for="label in labels">
|
||||
<span>{{ label.title }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="label-wrapper">
|
||||
<span
|
||||
v-for="label in labels"
|
||||
:key="label.id"
|
||||
:style="{'background': label.hexColor, 'color': label.textColor}"
|
||||
class="tag"
|
||||
>
|
||||
<span>{{ label.title }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
<template>
|
||||
<Multiselect
|
||||
class="control is-expanded"
|
||||
:placeholder="$t('list.search')"
|
||||
@search="findLists"
|
||||
:search-results="foundLists"
|
||||
@select="select"
|
||||
label="title"
|
||||
v-model="list"
|
||||
:select-placeholder="$t('list.searchSelect')"
|
||||
>
|
||||
<template #searchResult="props">
|
||||
<span class="list-namespace-title search-result">{{ namespace(props.option.namespaceId) }} ></span>
|
||||
{{ props.option.title }}
|
||||
</template>
|
||||
</Multiselect>
|
||||
<Multiselect
|
||||
v-model="list"
|
||||
class="control is-expanded"
|
||||
:placeholder="$t('list.search')"
|
||||
:search-results="foundLists"
|
||||
label="title"
|
||||
:select-placeholder="$t('list.searchSelect')"
|
||||
@search="findLists"
|
||||
@select="select"
|
||||
>
|
||||
<template #searchResult="props">
|
||||
<span class="list-namespace-title search-result">{{ namespace(props.option.namespaceId) }} ></span>
|
||||
{{ props.option.title }}
|
||||
</template>
|
||||
</Multiselect>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,22 +1,44 @@
|
|||
<template>
|
||||
<div class="select">
|
||||
<select
|
||||
v-model.number="percentDone"
|
||||
:disabled="disabled || undefined"
|
||||
>
|
||||
<option value="0">0%</option>
|
||||
<option value="0.1">10%</option>
|
||||
<option value="0.2">20%</option>
|
||||
<option value="0.3">30%</option>
|
||||
<option value="0.4">40%</option>
|
||||
<option value="0.5">50%</option>
|
||||
<option value="0.6">60%</option>
|
||||
<option value="0.7">70%</option>
|
||||
<option value="0.8">80%</option>
|
||||
<option value="0.9">90%</option>
|
||||
<option value="1">100%</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select">
|
||||
<select
|
||||
v-model.number="percentDone"
|
||||
:disabled="disabled || undefined"
|
||||
>
|
||||
<option value="0">
|
||||
0%
|
||||
</option>
|
||||
<option value="0.1">
|
||||
10%
|
||||
</option>
|
||||
<option value="0.2">
|
||||
20%
|
||||
</option>
|
||||
<option value="0.3">
|
||||
30%
|
||||
</option>
|
||||
<option value="0.4">
|
||||
40%
|
||||
</option>
|
||||
<option value="0.5">
|
||||
50%
|
||||
</option>
|
||||
<option value="0.6">
|
||||
60%
|
||||
</option>
|
||||
<option value="0.7">
|
||||
70%
|
||||
</option>
|
||||
<option value="0.8">
|
||||
80%
|
||||
</option>
|
||||
<option value="0.9">
|
||||
90%
|
||||
</option>
|
||||
<option value="1">
|
||||
100%
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,23 +1,30 @@
|
|||
<template>
|
||||
<span
|
||||
:class="{'not-so-high': priority === priorities.HIGH, 'high-priority': priority >= priorities.HIGH}"
|
||||
class="priority-label"
|
||||
v-if="!done && (showAll || priority >= priorities.HIGH)">
|
||||
<span class="icon" v-if="priority >= priorities.HIGH">
|
||||
<icon icon="exclamation"/>
|
||||
</span>
|
||||
<span>
|
||||
<template v-if="priority === priorities.UNSET">{{ $t('task.priority.unset') }}</template>
|
||||
<template v-if="priority === priorities.LOW">{{ $t('task.priority.low') }}</template>
|
||||
<template v-if="priority === priorities.MEDIUM">{{ $t('task.priority.medium') }}</template>
|
||||
<template v-if="priority === priorities.HIGH">{{ $t('task.priority.high') }}</template>
|
||||
<template v-if="priority === priorities.URGENT">{{ $t('task.priority.urgent') }}</template>
|
||||
<template v-if="priority === priorities.DO_NOW">{{ $t('task.priority.doNow') }}</template>
|
||||
</span>
|
||||
<span class="icon" v-if="priority === priorities.DO_NOW">
|
||||
<icon icon="exclamation"/>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
v-if="!done && (showAll || priority >= priorities.HIGH)"
|
||||
:class="{'not-so-high': priority === priorities.HIGH, 'high-priority': priority >= priorities.HIGH}"
|
||||
class="priority-label"
|
||||
>
|
||||
<span
|
||||
v-if="priority >= priorities.HIGH"
|
||||
class="icon"
|
||||
>
|
||||
<icon icon="exclamation" />
|
||||
</span>
|
||||
<span>
|
||||
<template v-if="priority === priorities.UNSET">{{ $t('task.priority.unset') }}</template>
|
||||
<template v-if="priority === priorities.LOW">{{ $t('task.priority.low') }}</template>
|
||||
<template v-if="priority === priorities.MEDIUM">{{ $t('task.priority.medium') }}</template>
|
||||
<template v-if="priority === priorities.HIGH">{{ $t('task.priority.high') }}</template>
|
||||
<template v-if="priority === priorities.URGENT">{{ $t('task.priority.urgent') }}</template>
|
||||
<template v-if="priority === priorities.DO_NOW">{{ $t('task.priority.doNow') }}</template>
|
||||
</span>
|
||||
<span
|
||||
v-if="priority === priorities.DO_NOW"
|
||||
class="icon"
|
||||
>
|
||||
<icon icon="exclamation" />
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,18 +1,30 @@
|
|||
<template>
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="priority"
|
||||
@change="updateData"
|
||||
:disabled="disabled || undefined"
|
||||
>
|
||||
<option :value="PRIORITIES.UNSET">{{ $t('task.priority.unset') }}</option>
|
||||
<option :value="PRIORITIES.LOW">{{ $t('task.priority.low') }}</option>
|
||||
<option :value="PRIORITIES.MEDIUM">{{ $t('task.priority.medium') }}</option>
|
||||
<option :value="PRIORITIES.HIGH">{{ $t('task.priority.high') }}</option>
|
||||
<option :value="PRIORITIES.URGENT">{{ $t('task.priority.urgent') }}</option>
|
||||
<option :value="PRIORITIES.DO_NOW">{{ $t('task.priority.doNow') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="priority"
|
||||
:disabled="disabled || undefined"
|
||||
@change="updateData"
|
||||
>
|
||||
<option :value="PRIORITIES.UNSET">
|
||||
{{ $t('task.priority.unset') }}
|
||||
</option>
|
||||
<option :value="PRIORITIES.LOW">
|
||||
{{ $t('task.priority.low') }}
|
||||
</option>
|
||||
<option :value="PRIORITIES.MEDIUM">
|
||||
{{ $t('task.priority.medium') }}
|
||||
</option>
|
||||
<option :value="PRIORITIES.HIGH">
|
||||
{{ $t('task.priority.high') }}
|
||||
</option>
|
||||
<option :value="PRIORITIES.URGENT">
|
||||
{{ $t('task.priority.urgent') }}
|
||||
</option>
|
||||
<option :value="PRIORITIES.DO_NOW">
|
||||
{{ $t('task.priority.doNow') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,92 +1,97 @@
|
|||
<template>
|
||||
<div v-if="available">
|
||||
<p class="help has-text-grey">
|
||||
{{ $t('task.quickAddMagic.hint') }}.
|
||||
<ButtonLink @click="() => visible = true">{{ $t('task.quickAddMagic.what') }}</ButtonLink>
|
||||
</p>
|
||||
<modal
|
||||
@close="() => visible = false"
|
||||
:enabled="visible"
|
||||
transition-name="fade"
|
||||
:overflow="true"
|
||||
variant="hint-modal"
|
||||
>
|
||||
<card class="has-no-shadow" :title="$t('task.quickAddMagic.title')">
|
||||
<p>{{ $t('task.quickAddMagic.intro') }}</p>
|
||||
<div v-if="available">
|
||||
<p class="help has-text-grey">
|
||||
{{ $t('task.quickAddMagic.hint') }}.
|
||||
<ButtonLink @click="() => visible = true">
|
||||
{{ $t('task.quickAddMagic.what') }}
|
||||
</ButtonLink>
|
||||
</p>
|
||||
<modal
|
||||
:enabled="visible"
|
||||
transition-name="fade"
|
||||
:overflow="true"
|
||||
variant="hint-modal"
|
||||
@close="() => visible = false"
|
||||
>
|
||||
<card
|
||||
class="has-no-shadow"
|
||||
:title="$t('task.quickAddMagic.title')"
|
||||
>
|
||||
<p>{{ $t('task.quickAddMagic.intro') }}</p>
|
||||
|
||||
<h3>{{ $t('task.attributes.labels') }}</h3>
|
||||
<p>
|
||||
{{ $t('task.quickAddMagic.label1', {prefix: prefixes.label}) }}
|
||||
{{ $t('task.quickAddMagic.label2') }}
|
||||
{{ $t('task.quickAddMagic.multiple') }}
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('task.quickAddMagic.label3') }}
|
||||
{{ $t('task.quickAddMagic.label4', {prefix: prefixes.label}) }}
|
||||
</p>
|
||||
<h3>{{ $t('task.attributes.labels') }}</h3>
|
||||
<p>
|
||||
{{ $t('task.quickAddMagic.label1', {prefix: prefixes.label}) }}
|
||||
{{ $t('task.quickAddMagic.label2') }}
|
||||
{{ $t('task.quickAddMagic.multiple') }}
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('task.quickAddMagic.label3') }}
|
||||
{{ $t('task.quickAddMagic.label4', {prefix: prefixes.label}) }}
|
||||
</p>
|
||||
|
||||
<h3>{{ $t('task.attributes.priority') }}</h3>
|
||||
<p>
|
||||
{{ $t('task.quickAddMagic.priority1', {prefix: prefixes.priority}) }}
|
||||
{{ $t('task.quickAddMagic.priority2') }}
|
||||
</p>
|
||||
<h3>{{ $t('task.attributes.priority') }}</h3>
|
||||
<p>
|
||||
{{ $t('task.quickAddMagic.priority1', {prefix: prefixes.priority}) }}
|
||||
{{ $t('task.quickAddMagic.priority2') }}
|
||||
</p>
|
||||
|
||||
<h3>{{ $t('task.attributes.assignees') }}</h3>
|
||||
<p>
|
||||
{{ $t('task.quickAddMagic.assignees', {prefix: prefixes.assignee}) }}
|
||||
{{ $t('task.quickAddMagic.multiple') }}
|
||||
</p>
|
||||
<h3>{{ $t('task.attributes.assignees') }}</h3>
|
||||
<p>
|
||||
{{ $t('task.quickAddMagic.assignees', {prefix: prefixes.assignee}) }}
|
||||
{{ $t('task.quickAddMagic.multiple') }}
|
||||
</p>
|
||||
|
||||
<h3>{{ $t('list.list.title') }}</h3>
|
||||
<p>
|
||||
{{ $t('task.quickAddMagic.list1', {prefix: prefixes.list}) }}
|
||||
{{ $t('task.quickAddMagic.list2') }}
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('task.quickAddMagic.list3') }}
|
||||
{{ $t('task.quickAddMagic.list4', {prefix: prefixes.list}) }}
|
||||
</p>
|
||||
<h3>{{ $t('list.list.title') }}</h3>
|
||||
<p>
|
||||
{{ $t('task.quickAddMagic.list1', {prefix: prefixes.list}) }}
|
||||
{{ $t('task.quickAddMagic.list2') }}
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('task.quickAddMagic.list3') }}
|
||||
{{ $t('task.quickAddMagic.list4', {prefix: prefixes.list}) }}
|
||||
</p>
|
||||
|
||||
<h3>{{ $t('task.quickAddMagic.dateAndTime') }}</h3>
|
||||
<p>
|
||||
{{ $t('task.quickAddMagic.date') }}
|
||||
</p>
|
||||
<ul>
|
||||
<!-- Not localized because these only work in english -->
|
||||
<li>Today</li>
|
||||
<li>Tomorrow</li>
|
||||
<li>Next monday</li>
|
||||
<li>This weekend</li>
|
||||
<li>Later this week</li>
|
||||
<li>Later next week</li>
|
||||
<li>Next week</li>
|
||||
<li>Next month</li>
|
||||
<li>End of month</li>
|
||||
<li>In 5 days [hours/weeks/months]</li>
|
||||
<li>Tuesday ({{ $t('task.quickAddMagic.dateWeekday') }})</li>
|
||||
<li>17/02/2021</li>
|
||||
<li>Feb 17 ({{ $t('task.quickAddMagic.dateCurrentYear') }})</li>
|
||||
<li>17th ({{ $t('task.quickAddMagic.dateNth', {day: '17'}) }})</li>
|
||||
</ul>
|
||||
<p>{{ $t('task.quickAddMagic.dateTime', {time: 'at 17:00', timePM: '5pm'}) }}</p>
|
||||
<h3>{{ $t('task.quickAddMagic.dateAndTime') }}</h3>
|
||||
<p>
|
||||
{{ $t('task.quickAddMagic.date') }}
|
||||
</p>
|
||||
<ul>
|
||||
<!-- Not localized because these only work in english -->
|
||||
<li>Today</li>
|
||||
<li>Tomorrow</li>
|
||||
<li>Next monday</li>
|
||||
<li>This weekend</li>
|
||||
<li>Later this week</li>
|
||||
<li>Later next week</li>
|
||||
<li>Next week</li>
|
||||
<li>Next month</li>
|
||||
<li>End of month</li>
|
||||
<li>In 5 days [hours/weeks/months]</li>
|
||||
<li>Tuesday ({{ $t('task.quickAddMagic.dateWeekday') }})</li>
|
||||
<li>17/02/2021</li>
|
||||
<li>Feb 17 ({{ $t('task.quickAddMagic.dateCurrentYear') }})</li>
|
||||
<li>17th ({{ $t('task.quickAddMagic.dateNth', {day: '17'}) }})</li>
|
||||
</ul>
|
||||
<p>{{ $t('task.quickAddMagic.dateTime', {time: 'at 17:00', timePM: '5pm'}) }}</p>
|
||||
|
||||
<h3>{{ $t('task.quickAddMagic.repeats') }}</h3>
|
||||
<p>{{ $t('task.quickAddMagic.repeatsDescription', {suffix: 'every {amount} {type}'}) }}</p>
|
||||
<p>{{ $t('misc.forExample') }}</p>
|
||||
<ul>
|
||||
<!-- Not localized because these only work in english -->
|
||||
<li>Every day</li>
|
||||
<li>Every 3 days</li>
|
||||
<li>Every week</li>
|
||||
<li>Every 2 weeks</li>
|
||||
<li>Every month</li>
|
||||
<li>Every 6 months</li>
|
||||
<li>Every year</li>
|
||||
<li>Every 2 years</li>
|
||||
</ul>
|
||||
</card>
|
||||
</modal>
|
||||
</div>
|
||||
<h3>{{ $t('task.quickAddMagic.repeats') }}</h3>
|
||||
<p>{{ $t('task.quickAddMagic.repeatsDescription', {suffix: 'every {amount} {type}'}) }}</p>
|
||||
<p>{{ $t('misc.forExample') }}</p>
|
||||
<ul>
|
||||
<!-- Not localized because these only work in english -->
|
||||
<li>Every day</li>
|
||||
<li>Every 3 days</li>
|
||||
<li>Every week</li>
|
||||
<li>Every 2 weeks</li>
|
||||
<li>Every month</li>
|
||||
<li>Every 6 months</li>
|
||||
<li>Every year</li>
|
||||
<li>Every 2 years</li>
|
||||
</ul>
|
||||
</card>
|
||||
</modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,148 +1,194 @@
|
|||
<template>
|
||||
<div class="task-relations">
|
||||
<x-button
|
||||
v-if="editEnabled && Object.keys(relatedTasks).length > 0"
|
||||
@click="showNewRelationForm = !showNewRelationForm"
|
||||
class="is-pulled-right add-task-relation-button d-print-none"
|
||||
:class="{'is-active': showNewRelationForm}"
|
||||
v-tooltip="$t('task.relation.add')"
|
||||
variant="secondary"
|
||||
icon="plus"
|
||||
:shadow="false"
|
||||
/>
|
||||
<transition-group name="fade">
|
||||
<template v-if="editEnabled && showCreate">
|
||||
<label class="label" key="label">
|
||||
{{ $t('task.relation.new') }}
|
||||
<transition name="fade">
|
||||
<span class="is-inline-flex" v-if="taskRelationService.loading">
|
||||
<span class="loader is-inline-block mr-2"></span>
|
||||
{{ $t('misc.saving') }}
|
||||
</span>
|
||||
<span class="has-text-success" v-else-if="!taskRelationService.loading && saved">
|
||||
{{ $t('misc.saved') }}
|
||||
</span>
|
||||
</transition>
|
||||
</label>
|
||||
<div class="field" key="field-search">
|
||||
<Multiselect
|
||||
:placeholder="$t('task.relation.searchPlaceholder')"
|
||||
@search="findTasks"
|
||||
:loading="taskService.loading"
|
||||
:search-results="mappedFoundTasks"
|
||||
label="title"
|
||||
v-model="newTaskRelation.task"
|
||||
:creatable="true"
|
||||
:create-placeholder="$t('task.relation.createPlaceholder')"
|
||||
@create="createAndRelateTask"
|
||||
>
|
||||
<template #searchResult="{option: task}">
|
||||
<span v-if="typeof task !== 'string'" class="search-result">
|
||||
<span
|
||||
class="different-list"
|
||||
v-if="task.listId !== listId"
|
||||
>
|
||||
<span
|
||||
v-if="task.differentNamespace !== null"
|
||||
v-tooltip="$t('task.relation.differentNamespace')">
|
||||
{{ task.differentNamespace }} >
|
||||
</span>
|
||||
<span
|
||||
v-if="task.differentList !== null"
|
||||
v-tooltip="$t('task.relation.differentList')">
|
||||
{{ task.differentList }} >
|
||||
</span>
|
||||
</span>
|
||||
{{ task.title }}
|
||||
</span>
|
||||
<span class="search-result" v-else>
|
||||
{{ task }}
|
||||
</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
</div>
|
||||
<div class="field has-addons mb-4" key="field-kind">
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth has-defaults">
|
||||
<select v-model="newTaskRelation.kind">
|
||||
<option value="unset">{{ $t('task.relation.select') }}</option>
|
||||
<option :key="`option_${rk}`" :value="rk" v-for="rk in RELATION_KINDS">
|
||||
{{ $tc(`task.relation.kinds.${rk}`, 1) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button @click="addTaskRelation()">{{ $t('task.relation.add') }}</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</transition-group>
|
||||
<div class="task-relations">
|
||||
<x-button
|
||||
v-if="editEnabled && Object.keys(relatedTasks).length > 0"
|
||||
v-tooltip="$t('task.relation.add')"
|
||||
class="is-pulled-right add-task-relation-button d-print-none"
|
||||
:class="{'is-active': showNewRelationForm}"
|
||||
variant="secondary"
|
||||
icon="plus"
|
||||
:shadow="false"
|
||||
@click="showNewRelationForm = !showNewRelationForm"
|
||||
/>
|
||||
<transition-group name="fade">
|
||||
<template v-if="editEnabled && showCreate">
|
||||
<label
|
||||
key="label"
|
||||
class="label"
|
||||
>
|
||||
{{ $t('task.relation.new') }}
|
||||
<transition name="fade">
|
||||
<span
|
||||
v-if="taskRelationService.loading"
|
||||
class="is-inline-flex"
|
||||
>
|
||||
<span class="loader is-inline-block mr-2" />
|
||||
{{ $t('misc.saving') }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="!taskRelationService.loading && saved"
|
||||
class="has-text-success"
|
||||
>
|
||||
{{ $t('misc.saved') }}
|
||||
</span>
|
||||
</transition>
|
||||
</label>
|
||||
<div
|
||||
key="field-search"
|
||||
class="field"
|
||||
>
|
||||
<Multiselect
|
||||
v-model="newTaskRelation.task"
|
||||
:placeholder="$t('task.relation.searchPlaceholder')"
|
||||
:loading="taskService.loading"
|
||||
:search-results="mappedFoundTasks"
|
||||
label="title"
|
||||
:creatable="true"
|
||||
:create-placeholder="$t('task.relation.createPlaceholder')"
|
||||
@search="findTasks"
|
||||
@create="createAndRelateTask"
|
||||
>
|
||||
<template #searchResult="{option: task}">
|
||||
<span
|
||||
v-if="typeof task !== 'string'"
|
||||
class="search-result"
|
||||
>
|
||||
<span
|
||||
v-if="task.listId !== listId"
|
||||
class="different-list"
|
||||
>
|
||||
<span
|
||||
v-if="task.differentNamespace !== null"
|
||||
v-tooltip="$t('task.relation.differentNamespace')"
|
||||
>
|
||||
{{ task.differentNamespace }} >
|
||||
</span>
|
||||
<span
|
||||
v-if="task.differentList !== null"
|
||||
v-tooltip="$t('task.relation.differentList')"
|
||||
>
|
||||
{{ task.differentList }} >
|
||||
</span>
|
||||
</span>
|
||||
{{ task.title }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="search-result"
|
||||
>
|
||||
{{ task }}
|
||||
</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
</div>
|
||||
<div
|
||||
key="field-kind"
|
||||
class="field has-addons mb-4"
|
||||
>
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth has-defaults">
|
||||
<select v-model="newTaskRelation.kind">
|
||||
<option value="unset">
|
||||
{{ $t('task.relation.select') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="rk in RELATION_KINDS"
|
||||
:key="`option_${rk}`"
|
||||
:value="rk"
|
||||
>
|
||||
{{ $tc(`task.relation.kinds.${rk}`, 1) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button @click="addTaskRelation()">
|
||||
{{ $t('task.relation.add') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</transition-group>
|
||||
|
||||
<div :key="rts.kind" class="related-tasks" v-for="rts in mappedRelatedTasks">
|
||||
<span class="title">{{ rts.title }}</span>
|
||||
<div class="tasks">
|
||||
<div :key="t.id" class="task" v-for="t in rts.tasks">
|
||||
<div class="is-flex is-align-items-center">
|
||||
<Fancycheckbox
|
||||
class="task-done-checkbox"
|
||||
v-model="t.done"
|
||||
@update:model-value="toggleTaskDone(t)"
|
||||
/>
|
||||
<router-link
|
||||
:to="{ name: route.name as string, params: { id: t.id } }"
|
||||
:class="{ 'is-strikethrough': t.done}"
|
||||
>
|
||||
<span
|
||||
class="different-list"
|
||||
v-if="t.listId !== listId"
|
||||
>
|
||||
<span
|
||||
v-if="t.differentNamespace !== null"
|
||||
v-tooltip="$t('task.relation.differentNamespace')">
|
||||
{{ t.differentNamespace }} >
|
||||
</span>
|
||||
<span
|
||||
v-if="t.differentList !== null"
|
||||
v-tooltip="$t('task.relation.differentList')">
|
||||
{{ t.differentList }} >
|
||||
</span>
|
||||
</span>
|
||||
{{ t.title }}
|
||||
</router-link>
|
||||
</div>
|
||||
<BaseButton
|
||||
v-if="editEnabled"
|
||||
@click="setRelationToDelete({
|
||||
relationKind: rts.kind,
|
||||
otherTaskId: t.id
|
||||
})"
|
||||
class="remove"
|
||||
>
|
||||
<icon icon="trash-alt"/>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="none" v-if="showNoRelationsNotice && Object.keys(relatedTasks).length === 0">
|
||||
{{ $t('task.relation.noneYet') }}
|
||||
</p>
|
||||
<div
|
||||
v-for="rts in mappedRelatedTasks"
|
||||
:key="rts.kind"
|
||||
class="related-tasks"
|
||||
>
|
||||
<span class="title">{{ rts.title }}</span>
|
||||
<div class="tasks">
|
||||
<div
|
||||
v-for="t in rts.tasks"
|
||||
:key="t.id"
|
||||
class="task"
|
||||
>
|
||||
<div class="is-flex is-align-items-center">
|
||||
<Fancycheckbox
|
||||
v-model="t.done"
|
||||
class="task-done-checkbox"
|
||||
@update:model-value="toggleTaskDone(t)"
|
||||
/>
|
||||
<router-link
|
||||
:to="{ name: route.name as string, params: { id: t.id } }"
|
||||
:class="{ 'is-strikethrough': t.done}"
|
||||
>
|
||||
<span
|
||||
v-if="t.listId !== listId"
|
||||
class="different-list"
|
||||
>
|
||||
<span
|
||||
v-if="t.differentNamespace !== null"
|
||||
v-tooltip="$t('task.relation.differentNamespace')"
|
||||
>
|
||||
{{ t.differentNamespace }} >
|
||||
</span>
|
||||
<span
|
||||
v-if="t.differentList !== null"
|
||||
v-tooltip="$t('task.relation.differentList')"
|
||||
>
|
||||
{{ t.differentList }} >
|
||||
</span>
|
||||
</span>
|
||||
{{ t.title }}
|
||||
</router-link>
|
||||
</div>
|
||||
<BaseButton
|
||||
v-if="editEnabled"
|
||||
class="remove"
|
||||
@click="setRelationToDelete({
|
||||
relationKind: rts.kind,
|
||||
otherTaskId: t.id
|
||||
})"
|
||||
>
|
||||
<icon icon="trash-alt" />
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
v-if="showNoRelationsNotice && Object.keys(relatedTasks).length === 0"
|
||||
class="none"
|
||||
>
|
||||
{{ $t('task.relation.noneYet') }}
|
||||
</p>
|
||||
|
||||
<modal
|
||||
v-if="relationToDelete !== undefined"
|
||||
@close="relationToDelete = undefined"
|
||||
@submit="removeTaskRelation()"
|
||||
>
|
||||
<template #header><span>{{ $t('task.relation.delete') }}</span></template>
|
||||
<modal
|
||||
v-if="relationToDelete !== undefined"
|
||||
@close="relationToDelete = undefined"
|
||||
@submit="removeTaskRelation()"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{ $t('task.relation.delete') }}</span>
|
||||
</template>
|
||||
|
||||
<template #text>
|
||||
<p>
|
||||
{{ $t('task.relation.deleteText1') }}<br/>
|
||||
<strong class="has-text-white">{{ $t('misc.cannotBeUndone') }}</strong>
|
||||
</p>
|
||||
</template>
|
||||
</modal>
|
||||
</div>
|
||||
<template #text>
|
||||
<p>
|
||||
{{ $t('task.relation.deleteText1') }}<br>
|
||||
<strong class="has-text-white">{{ $t('misc.cannotBeUndone') }}</strong>
|
||||
</p>
|
||||
</template>
|
||||
</modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,28 +1,35 @@
|
|||
<template>
|
||||
<div class="reminders">
|
||||
<div
|
||||
v-for="(r, index) in reminders"
|
||||
:key="index"
|
||||
:class="{ 'overdue': r < new Date()}"
|
||||
class="reminder-input"
|
||||
>
|
||||
<Datepicker
|
||||
v-model="reminders[index]"
|
||||
:disabled="disabled"
|
||||
@close-on-change="() => addReminderDate(index)"
|
||||
/>
|
||||
<BaseButton @click="removeReminderByIndex(index)" v-if="!disabled" class="remove">
|
||||
<icon icon="times"></icon>
|
||||
</BaseButton>
|
||||
</div>
|
||||
<div class="reminder-input" v-if="!disabled">
|
||||
<Datepicker
|
||||
v-model="newReminder"
|
||||
@close-on-change="() => addReminderDate()"
|
||||
:choose-date-label="$t('task.addReminder')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="reminders">
|
||||
<div
|
||||
v-for="(r, index) in reminders"
|
||||
:key="index"
|
||||
:class="{ 'overdue': r < new Date()}"
|
||||
class="reminder-input"
|
||||
>
|
||||
<Datepicker
|
||||
v-model="reminders[index]"
|
||||
:disabled="disabled"
|
||||
@close-on-change="() => addReminderDate(index)"
|
||||
/>
|
||||
<BaseButton
|
||||
v-if="!disabled"
|
||||
class="remove"
|
||||
@click="removeReminderByIndex(index)"
|
||||
>
|
||||
<icon icon="times" />
|
||||
</BaseButton>
|
||||
</div>
|
||||
<div
|
||||
v-if="!disabled"
|
||||
class="reminder-input"
|
||||
>
|
||||
<Datepicker
|
||||
v-model="newReminder"
|
||||
:choose-date-label="$t('task.addReminder')"
|
||||
@close-on-change="() => addReminderDate()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,64 +1,102 @@
|
|||
<template>
|
||||
<div class="control repeat-after-input">
|
||||
<div class="buttons has-addons is-centered mt-2">
|
||||
<x-button variant="secondary" class="is-small" @click="() => setRepeatAfter(1, 'days')">
|
||||
{{ $t('task.repeat.everyDay') }}
|
||||
</x-button>
|
||||
<x-button variant="secondary" class="is-small" @click="() => setRepeatAfter(1, 'weeks')">
|
||||
{{ $t('task.repeat.everyWeek') }}
|
||||
</x-button>
|
||||
<x-button variant="secondary" class="is-small" @click="() => setRepeatAfter(1, 'months')">
|
||||
{{ $t('task.repeat.everyMonth') }}
|
||||
</x-button>
|
||||
</div>
|
||||
<div class="is-flex is-align-items-center mb-2">
|
||||
<label for="repeatMode" class="is-fullwidth">
|
||||
{{ $t('task.repeat.mode') }}:
|
||||
</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select @change="updateData" v-model="task.repeatMode" id="repeatMode">
|
||||
<option :value="TASK_REPEAT_MODES.REPEAT_MODE_DEFAULT">{{ $t('misc.default') }}</option>
|
||||
<option :value="TASK_REPEAT_MODES.REPEAT_MODE_MONTH">{{ $t('task.repeat.monthly') }}</option>
|
||||
<option :value="TASK_REPEAT_MODES.REPEAT_MODE_FROM_CURRENT_DATE">{{ $t('task.repeat.fromCurrentDate') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="is-flex" v-if="task.repeatMode !== TASK_REPEAT_MODES.REPEAT_MODE_MONTH">
|
||||
<p class="pr-4">
|
||||
{{ $t('task.repeat.each') }}
|
||||
</p>
|
||||
<div class="field has-addons is-fullwidth">
|
||||
<div class="control">
|
||||
<input
|
||||
:disabled="disabled || undefined"
|
||||
@change="updateData"
|
||||
class="input"
|
||||
:placeholder="$t('task.repeat.specifyAmount')"
|
||||
v-model="repeatAfter.amount"
|
||||
type="number"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="repeatAfter.type"
|
||||
@change="updateData"
|
||||
:disabled="disabled || undefined"
|
||||
>
|
||||
<option value="hours">{{ $t('task.repeat.hours') }}</option>
|
||||
<option value="days">{{ $t('task.repeat.days') }}</option>
|
||||
<option value="weeks">{{ $t('task.repeat.weeks') }}</option>
|
||||
<option value="months">{{ $t('task.repeat.months') }}</option>
|
||||
<option value="years">{{ $t('task.repeat.years') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control repeat-after-input">
|
||||
<div class="buttons has-addons is-centered mt-2">
|
||||
<x-button
|
||||
variant="secondary"
|
||||
class="is-small"
|
||||
@click="() => setRepeatAfter(1, 'days')"
|
||||
>
|
||||
{{ $t('task.repeat.everyDay') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
class="is-small"
|
||||
@click="() => setRepeatAfter(1, 'weeks')"
|
||||
>
|
||||
{{ $t('task.repeat.everyWeek') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
class="is-small"
|
||||
@click="() => setRepeatAfter(1, 'months')"
|
||||
>
|
||||
{{ $t('task.repeat.everyMonth') }}
|
||||
</x-button>
|
||||
</div>
|
||||
<div class="is-flex is-align-items-center mb-2">
|
||||
<label
|
||||
for="repeatMode"
|
||||
class="is-fullwidth"
|
||||
>
|
||||
{{ $t('task.repeat.mode') }}:
|
||||
</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select
|
||||
id="repeatMode"
|
||||
v-model="task.repeatMode"
|
||||
@change="updateData"
|
||||
>
|
||||
<option :value="TASK_REPEAT_MODES.REPEAT_MODE_DEFAULT">
|
||||
{{ $t('misc.default') }}
|
||||
</option>
|
||||
<option :value="TASK_REPEAT_MODES.REPEAT_MODE_MONTH">
|
||||
{{ $t('task.repeat.monthly') }}
|
||||
</option>
|
||||
<option :value="TASK_REPEAT_MODES.REPEAT_MODE_FROM_CURRENT_DATE">
|
||||
{{ $t('task.repeat.fromCurrentDate') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="task.repeatMode !== TASK_REPEAT_MODES.REPEAT_MODE_MONTH"
|
||||
class="is-flex"
|
||||
>
|
||||
<p class="pr-4">
|
||||
{{ $t('task.repeat.each') }}
|
||||
</p>
|
||||
<div class="field has-addons is-fullwidth">
|
||||
<div class="control">
|
||||
<input
|
||||
v-model="repeatAfter.amount"
|
||||
:disabled="disabled || undefined"
|
||||
class="input"
|
||||
:placeholder="$t('task.repeat.specifyAmount')"
|
||||
type="number"
|
||||
min="0"
|
||||
@change="updateData"
|
||||
>
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="repeatAfter.type"
|
||||
:disabled="disabled || undefined"
|
||||
@change="updateData"
|
||||
>
|
||||
<option value="hours">
|
||||
{{ $t('task.repeat.hours') }}
|
||||
</option>
|
||||
<option value="days">
|
||||
{{ $t('task.repeat.days') }}
|
||||
</option>
|
||||
<option value="weeks">
|
||||
{{ $t('task.repeat.weeks') }}
|
||||
</option>
|
||||
<option value="months">
|
||||
{{ $t('task.repeat.months') }}
|
||||
</option>
|
||||
<option value="years">
|
||||
{{ $t('task.repeat.years') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,103 +1,145 @@
|
|||
<template>
|
||||
<div :class="{'is-loading': taskService.loading}" class="task loader-container">
|
||||
<fancycheckbox :disabled="(isArchived || disabled) && !canMarkAsDone" @change="markAsDone" v-model="task.done"/>
|
||||
<ColorBubble
|
||||
v-if="showListColor && listColor !== ''"
|
||||
:color="listColor"
|
||||
class="mr-1"
|
||||
/>
|
||||
<router-link
|
||||
:to="taskDetailRoute"
|
||||
:class="{ 'done': task.done}"
|
||||
class="tasktext">
|
||||
<span>
|
||||
<router-link
|
||||
:to="{ name: 'list.list', params: { listId: task.listId } }"
|
||||
class="task-list"
|
||||
:class="{'mr-2': task.hexColor !== ''}"
|
||||
v-if="showList && getListById(task.listId) !== null"
|
||||
v-tooltip="$t('task.detail.belongsToList', {list: getListById(task.listId).title})">
|
||||
{{ getListById(task.listId).title }}
|
||||
</router-link>
|
||||
<div
|
||||
:class="{'is-loading': taskService.loading}"
|
||||
class="task loader-container"
|
||||
>
|
||||
<fancycheckbox
|
||||
v-model="task.done"
|
||||
:disabled="(isArchived || disabled) && !canMarkAsDone"
|
||||
@change="markAsDone"
|
||||
/>
|
||||
<ColorBubble
|
||||
v-if="showListColor && listColor !== ''"
|
||||
:color="listColor"
|
||||
class="mr-1"
|
||||
/>
|
||||
<router-link
|
||||
:to="taskDetailRoute"
|
||||
:class="{ 'done': task.done}"
|
||||
class="tasktext"
|
||||
>
|
||||
<span>
|
||||
<router-link
|
||||
v-if="showList && getListById(task.listId) !== null"
|
||||
v-tooltip="$t('task.detail.belongsToList', {list: getListById(task.listId).title})"
|
||||
:to="{ name: 'list.list', params: { listId: task.listId } }"
|
||||
class="task-list"
|
||||
:class="{'mr-2': task.hexColor !== ''}"
|
||||
>
|
||||
{{ getListById(task.listId).title }}
|
||||
</router-link>
|
||||
|
||||
<ColorBubble
|
||||
v-if="task.hexColor !== ''"
|
||||
:color="task.getHexColor()"
|
||||
class="mr-1"
|
||||
/>
|
||||
<!-- Show any parent tasks to make it clear this task is a sub task of something -->
|
||||
<span class="parent-tasks" v-if="typeof task.relatedTasks.parenttask !== 'undefined'">
|
||||
<template v-for="(pt, i) in task.relatedTasks.parenttask">
|
||||
{{ pt.title }}<template v-if="(i + 1) < task.relatedTasks.parenttask.length">, </template>
|
||||
</template>
|
||||
>
|
||||
</span>
|
||||
{{ task.title }}
|
||||
</span>
|
||||
<ColorBubble
|
||||
v-if="task.hexColor !== ''"
|
||||
:color="task.getHexColor()"
|
||||
class="mr-1"
|
||||
/>
|
||||
<!-- Show any parent tasks to make it clear this task is a sub task of something -->
|
||||
<span
|
||||
v-if="typeof task.relatedTasks.parenttask !== 'undefined'"
|
||||
class="parent-tasks"
|
||||
>
|
||||
<template v-for="(pt, i) in task.relatedTasks.parenttask">
|
||||
{{ pt.title }}<template v-if="(i + 1) < task.relatedTasks.parenttask.length">, </template>
|
||||
</template>
|
||||
>
|
||||
</span>
|
||||
{{ task.title }}
|
||||
</span>
|
||||
|
||||
<labels class="labels ml-2 mr-1" :labels="task.labels" v-if="task.labels.length > 0" />
|
||||
<user
|
||||
:avatar-size="27"
|
||||
:is-inline="true"
|
||||
:key="task.id + 'assignee' + a.id + i"
|
||||
:show-username="false"
|
||||
:user="a"
|
||||
v-for="(a, i) in task.assignees"
|
||||
/>
|
||||
<BaseButton
|
||||
v-if="+new Date(task.dueDate) > 0"
|
||||
class="dueDate"
|
||||
@click.prevent.stop="showDefer = !showDefer"
|
||||
v-tooltip="formatDateLong(task.dueDate)"
|
||||
>
|
||||
<time
|
||||
:datetime="formatISO(task.dueDate)"
|
||||
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
||||
class="is-italic"
|
||||
:aria-expanded="showDefer ? 'true' : 'false'"
|
||||
>
|
||||
- {{ $t('task.detail.due', {at: formatDateSince(task.dueDate)}) }}
|
||||
</time>
|
||||
</BaseButton>
|
||||
<transition name="fade">
|
||||
<defer-task v-if="+new Date(task.dueDate) > 0 && showDefer" v-model="task" ref="deferDueDate"/>
|
||||
</transition>
|
||||
<priority-label :priority="task.priority" :done="task.done"/>
|
||||
<span>
|
||||
<span class="list-task-icon" v-if="task.attachments.length > 0">
|
||||
<icon icon="paperclip"/>
|
||||
</span>
|
||||
<span class="list-task-icon" v-if="task.description">
|
||||
<icon icon="align-left"/>
|
||||
</span>
|
||||
<span class="list-task-icon" v-if="task.repeatAfter.amount > 0">
|
||||
<icon icon="history"/>
|
||||
</span>
|
||||
</span>
|
||||
<checklist-summary :task="task"/>
|
||||
</router-link>
|
||||
<progress
|
||||
class="progress is-small"
|
||||
v-if="task.percentDone > 0"
|
||||
:value="task.percentDone * 100" max="100">
|
||||
{{ task.percentDone * 100 }}%
|
||||
</progress>
|
||||
<router-link
|
||||
:to="{ name: 'list.list', params: { listId: task.listId } }"
|
||||
class="task-list"
|
||||
v-if="!showList && currentList.id !== task.listId && getListById(task.listId) !== null"
|
||||
v-tooltip="$t('task.detail.belongsToList', {list: getListById(task.listId).title})">
|
||||
{{ getListById(task.listId).title }}
|
||||
</router-link>
|
||||
<BaseButton
|
||||
:class="{'is-favorite': task.isFavorite}"
|
||||
@click="toggleFavorite"
|
||||
class="favorite">
|
||||
<icon icon="star" v-if="task.isFavorite"/>
|
||||
<icon :icon="['far', 'star']" v-else/>
|
||||
</BaseButton>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<labels
|
||||
v-if="task.labels.length > 0"
|
||||
class="labels ml-2 mr-1"
|
||||
:labels="task.labels"
|
||||
/>
|
||||
<user
|
||||
v-for="(a, i) in task.assignees"
|
||||
:key="task.id + 'assignee' + a.id + i"
|
||||
:avatar-size="27"
|
||||
:is-inline="true"
|
||||
:show-username="false"
|
||||
:user="a"
|
||||
/>
|
||||
<BaseButton
|
||||
v-if="+new Date(task.dueDate) > 0"
|
||||
v-tooltip="formatDateLong(task.dueDate)"
|
||||
class="dueDate"
|
||||
@click.prevent.stop="showDefer = !showDefer"
|
||||
>
|
||||
<time
|
||||
:datetime="formatISO(task.dueDate)"
|
||||
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
||||
class="is-italic"
|
||||
:aria-expanded="showDefer ? 'true' : 'false'"
|
||||
>
|
||||
- {{ $t('task.detail.due', {at: formatDateSince(task.dueDate)}) }}
|
||||
</time>
|
||||
</BaseButton>
|
||||
<transition name="fade">
|
||||
<defer-task
|
||||
v-if="+new Date(task.dueDate) > 0 && showDefer"
|
||||
ref="deferDueDate"
|
||||
v-model="task"
|
||||
/>
|
||||
</transition>
|
||||
<priority-label
|
||||
:priority="task.priority"
|
||||
:done="task.done"
|
||||
/>
|
||||
<span>
|
||||
<span
|
||||
v-if="task.attachments.length > 0"
|
||||
class="list-task-icon"
|
||||
>
|
||||
<icon icon="paperclip" />
|
||||
</span>
|
||||
<span
|
||||
v-if="task.description"
|
||||
class="list-task-icon"
|
||||
>
|
||||
<icon icon="align-left" />
|
||||
</span>
|
||||
<span
|
||||
v-if="task.repeatAfter.amount > 0"
|
||||
class="list-task-icon"
|
||||
>
|
||||
<icon icon="history" />
|
||||
</span>
|
||||
</span>
|
||||
<checklist-summary :task="task" />
|
||||
</router-link>
|
||||
<progress
|
||||
v-if="task.percentDone > 0"
|
||||
class="progress is-small"
|
||||
:value="task.percentDone * 100"
|
||||
max="100"
|
||||
>
|
||||
{{ task.percentDone * 100 }}%
|
||||
</progress>
|
||||
<router-link
|
||||
v-if="!showList && currentList.id !== task.listId && getListById(task.listId) !== null"
|
||||
v-tooltip="$t('task.detail.belongsToList', {list: getListById(task.listId).title})"
|
||||
:to="{ name: 'list.list', params: { listId: task.listId } }"
|
||||
class="task-list"
|
||||
>
|
||||
{{ getListById(task.listId).title }}
|
||||
</router-link>
|
||||
<BaseButton
|
||||
:class="{'is-favorite': task.isFavorite}"
|
||||
class="favorite"
|
||||
@click="toggleFavorite"
|
||||
>
|
||||
<icon
|
||||
v-if="task.isFavorite"
|
||||
icon="star"
|
||||
/>
|
||||
<icon
|
||||
v-else
|
||||
:icon="['far', 'star']"
|
||||
/>
|
||||
</BaseButton>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -123,14 +165,7 @@ import {useBaseStore} from '@/stores/base'
|
|||
import {useTaskStore} from '@/stores/tasks'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'singleTaskInList',
|
||||
data() {
|
||||
return {
|
||||
taskService: new TaskService(),
|
||||
task: new TaskModel(),
|
||||
showDefer: false,
|
||||
}
|
||||
},
|
||||
name: 'SingleTaskInList',
|
||||
components: {
|
||||
ColorBubble,
|
||||
BaseButton,
|
||||
|
@ -168,6 +203,13 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
emits: ['task-updated'],
|
||||
data() {
|
||||
return {
|
||||
taskService: new TaskService(),
|
||||
task: new TaskModel(),
|
||||
showDefer: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
theTask(newVal) {
|
||||
this.task = newVal
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
<template>
|
||||
<BaseButton>
|
||||
<icon icon="sort-up" v-if="order === 'asc'"/>
|
||||
<icon icon="sort-up" v-else-if="order === 'desc'" rotation="180"/>
|
||||
<icon icon="sort" v-else/>
|
||||
</BaseButton>
|
||||
<BaseButton>
|
||||
<icon
|
||||
v-if="order === 'asc'"
|
||||
icon="sort-up"
|
||||
/>
|
||||
<icon
|
||||
v-else-if="order === 'desc'"
|
||||
icon="sort-up"
|
||||
rotation="180"
|
||||
/>
|
||||
<icon
|
||||
v-else
|
||||
icon="sort"
|
||||
/>
|
||||
</BaseButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -61,10 +61,10 @@ import Button from '@/components/input/button.vue'
|
|||
import Modal from '@/components/misc/modal.vue'
|
||||
import Card from '@/components/misc/card.vue'
|
||||
|
||||
app.component('icon', FontAwesomeIcon)
|
||||
app.component('x-button', Button)
|
||||
app.component('modal', Modal)
|
||||
app.component('card', Card)
|
||||
app.component('Icon', FontAwesomeIcon)
|
||||
app.component('XButton', Button)
|
||||
app.component('Modal', Modal)
|
||||
app.component('Card', Card)
|
||||
|
||||
app.config.errorHandler = (err, vm, info) => {
|
||||
if (import.meta.env.DEV) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div class="content has-text-centered">
|
||||
<h1>{{ $t('404.title') }}</h1>
|
||||
<p>{{ $t('404.text') }}</p>
|
||||
</div>
|
||||
<div class="content has-text-centered">
|
||||
<h1>{{ $t('404.title') }}</h1>
|
||||
<p>{{ $t('404.text') }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
transition-name="fade"
|
||||
variant="hint-modal"
|
||||
>
|
||||
<card
|
||||
class="has-no-shadow"
|
||||
:title="$t('about.title')"
|
||||
:has-close="true"
|
||||
@close="$router.back()"
|
||||
:padding="false"
|
||||
>
|
||||
<div class="p-4">
|
||||
<p>
|
||||
{{ $t('about.frontendVersion', {version: frontendVersion}) }}
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('about.apiVersion', {version: apiVersion}) }}
|
||||
</p>
|
||||
</div>
|
||||
<template #footer>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
@click.prevent.stop="$router.back()"
|
||||
>
|
||||
{{ $t('misc.close') }}
|
||||
</x-button>
|
||||
</template>
|
||||
</card>
|
||||
</modal>
|
||||
<modal
|
||||
transition-name="fade"
|
||||
variant="hint-modal"
|
||||
@close="$router.back()"
|
||||
>
|
||||
<card
|
||||
class="has-no-shadow"
|
||||
:title="$t('about.title')"
|
||||
:has-close="true"
|
||||
:padding="false"
|
||||
@close="$router.back()"
|
||||
>
|
||||
<div class="p-4">
|
||||
<p>
|
||||
{{ $t('about.frontendVersion', {version: frontendVersion}) }}
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('about.apiVersion', {version: apiVersion}) }}
|
||||
</p>
|
||||
</div>
|
||||
<template #footer>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
@click.prevent.stop="$router.back()"
|
||||
>
|
||||
{{ $t('misc.close') }}
|
||||
</x-button>
|
||||
</template>
|
||||
</card>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,61 +1,74 @@
|
|||
<template>
|
||||
<div class="content has-text-centered">
|
||||
<h2 v-if="userInfo">
|
||||
{{ $t(welcome, {username: userInfo.name !== '' ? userInfo.name : userInfo.username}) }}!
|
||||
</h2>
|
||||
<message variant="danger" v-if="deletionScheduledAt !== null" class="mb-4">
|
||||
{{
|
||||
$t('user.deletion.scheduled', {
|
||||
date: formatDateShort(deletionScheduledAt),
|
||||
dateSince: formatDateSince(deletionScheduledAt),
|
||||
})
|
||||
}}
|
||||
<router-link :to="{name: 'user.settings', hash: '#deletion'}">
|
||||
{{ $t('user.deletion.scheduledCancel') }}
|
||||
</router-link>
|
||||
</message>
|
||||
<add-task
|
||||
:listId="defaultListId"
|
||||
@taskAdded="updateTaskList"
|
||||
class="is-max-width-desktop"
|
||||
/>
|
||||
<template v-if="!hasTasks && !loading">
|
||||
<template v-if="defaultNamespaceId > 0">
|
||||
<p class="mt-4">{{ $t('home.list.newText') }}</p>
|
||||
<x-button
|
||||
:to="{ name: 'list.create', params: { namespaceId: defaultNamespaceId } }"
|
||||
:shadow="false"
|
||||
class="ml-2"
|
||||
>
|
||||
{{ $t('home.list.new') }}
|
||||
</x-button>
|
||||
</template>
|
||||
<p class="mt-4" v-if="migratorsEnabled">
|
||||
{{ $t('home.list.importText') }}
|
||||
</p>
|
||||
<x-button
|
||||
v-if="migratorsEnabled"
|
||||
:to="{ name: 'migrate.start' }"
|
||||
:shadow="false">
|
||||
{{ $t('home.list.import') }}
|
||||
</x-button>
|
||||
</template>
|
||||
<div v-if="listHistory.length > 0" class="is-max-width-desktop has-text-left mt-4">
|
||||
<h3>{{ $t('home.lastViewed') }}</h3>
|
||||
<div class="is-flex list-cards-wrapper-2-rows">
|
||||
<list-card
|
||||
v-for="(l, k) in listHistory"
|
||||
:key="`l${k}`"
|
||||
:list="l"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ShowTasks
|
||||
v-if="hasLists"
|
||||
class="mt-4"
|
||||
:key="showTasksKey"
|
||||
/>
|
||||
</div>
|
||||
<div class="content has-text-centered">
|
||||
<h2 v-if="userInfo">
|
||||
{{ $t(welcome, {username: userInfo.name !== '' ? userInfo.name : userInfo.username}) }}!
|
||||
</h2>
|
||||
<message
|
||||
v-if="deletionScheduledAt !== null"
|
||||
variant="danger"
|
||||
class="mb-4"
|
||||
>
|
||||
{{
|
||||
$t('user.deletion.scheduled', {
|
||||
date: formatDateShort(deletionScheduledAt),
|
||||
dateSince: formatDateSince(deletionScheduledAt),
|
||||
})
|
||||
}}
|
||||
<router-link :to="{name: 'user.settings', hash: '#deletion'}">
|
||||
{{ $t('user.deletion.scheduledCancel') }}
|
||||
</router-link>
|
||||
</message>
|
||||
<add-task
|
||||
:list-id="defaultListId"
|
||||
class="is-max-width-desktop"
|
||||
@taskAdded="updateTaskList"
|
||||
/>
|
||||
<template v-if="!hasTasks && !loading">
|
||||
<template v-if="defaultNamespaceId > 0">
|
||||
<p class="mt-4">
|
||||
{{ $t('home.list.newText') }}
|
||||
</p>
|
||||
<x-button
|
||||
:to="{ name: 'list.create', params: { namespaceId: defaultNamespaceId } }"
|
||||
:shadow="false"
|
||||
class="ml-2"
|
||||
>
|
||||
{{ $t('home.list.new') }}
|
||||
</x-button>
|
||||
</template>
|
||||
<p
|
||||
v-if="migratorsEnabled"
|
||||
class="mt-4"
|
||||
>
|
||||
{{ $t('home.list.importText') }}
|
||||
</p>
|
||||
<x-button
|
||||
v-if="migratorsEnabled"
|
||||
:to="{ name: 'migrate.start' }"
|
||||
:shadow="false"
|
||||
>
|
||||
{{ $t('home.list.import') }}
|
||||
</x-button>
|
||||
</template>
|
||||
<div
|
||||
v-if="listHistory.length > 0"
|
||||
class="is-max-width-desktop has-text-left mt-4"
|
||||
>
|
||||
<h3>{{ $t('home.lastViewed') }}</h3>
|
||||
<div class="is-flex list-cards-wrapper-2-rows">
|
||||
<list-card
|
||||
v-for="(l, k) in listHistory"
|
||||
:key="`l${k}`"
|
||||
:list="l"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ShowTasks
|
||||
v-if="hasLists"
|
||||
:key="showTasksKey"
|
||||
class="mt-4"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
@submit="deleteSavedFilter()"
|
||||
>
|
||||
<template #header><span>{{ $t('filters.delete.header') }}</span></template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
@submit="deleteSavedFilter()"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{ $t('filters.delete.header') }}</span>
|
||||
</template>
|
||||
|
||||
<template #text>
|
||||
<p>{{ $t('filters.delete.text') }}</p>
|
||||
</template>
|
||||
</modal>
|
||||
<template #text>
|
||||
<p>{{ $t('filters.delete.text') }}</p>
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,54 +1,64 @@
|
|||
<template>
|
||||
<create-edit
|
||||
:title="$t('filters.edit.title')"
|
||||
primary-icon=""
|
||||
:primary-label="$t('misc.save')"
|
||||
@primary="saveSavedFilter"
|
||||
:tertiary="$t('misc.delete')"
|
||||
@tertiary="$router.push({ name: 'filter.settings.delete', params: { id: $route.params.listId } })"
|
||||
>
|
||||
<form @submit.prevent="saveSavedFilter()">
|
||||
<div class="field">
|
||||
<label class="label" for="title">{{ $t('filters.attributes.title') }}</label>
|
||||
<div class="control">
|
||||
<input
|
||||
:class="{ 'disabled': filterService.loading}"
|
||||
:disabled="filterService.loading || undefined"
|
||||
@keyup.enter="saveSavedFilter"
|
||||
class="input"
|
||||
id="title"
|
||||
:placeholder="$t('filters.attributes.titlePlaceholder')"
|
||||
type="text"
|
||||
v-focus
|
||||
v-model="filter.title"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="description">{{ $t('filters.attributes.description') }}</label>
|
||||
<div class="control">
|
||||
<editor
|
||||
:class="{ 'disabled': filterService.loading}"
|
||||
:disabled="filterService.loading"
|
||||
:preview-is-default="false"
|
||||
id="description"
|
||||
:placeholder="$t('filters.attributes.descriptionPlaceholder')"
|
||||
v-model="filter.description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="filters">{{ $t('filters.title') }}</label>
|
||||
<div class="control">
|
||||
<Filters
|
||||
:class="{ 'disabled': filterService.loading}"
|
||||
:disabled="filterService.loading"
|
||||
class="has-no-shadow has-no-border"
|
||||
v-model="filters"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</create-edit>
|
||||
<create-edit
|
||||
:title="$t('filters.edit.title')"
|
||||
primary-icon=""
|
||||
:primary-label="$t('misc.save')"
|
||||
:tertiary="$t('misc.delete')"
|
||||
@primary="saveSavedFilter"
|
||||
@tertiary="$router.push({ name: 'filter.settings.delete', params: { id: $route.params.listId } })"
|
||||
>
|
||||
<form @submit.prevent="saveSavedFilter()">
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="title"
|
||||
>{{ $t('filters.attributes.title') }}</label>
|
||||
<div class="control">
|
||||
<input
|
||||
id="title"
|
||||
v-model="filter.title"
|
||||
v-focus
|
||||
:class="{ 'disabled': filterService.loading}"
|
||||
:disabled="filterService.loading || undefined"
|
||||
class="input"
|
||||
:placeholder="$t('filters.attributes.titlePlaceholder')"
|
||||
type="text"
|
||||
@keyup.enter="saveSavedFilter"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="description"
|
||||
>{{ $t('filters.attributes.description') }}</label>
|
||||
<div class="control">
|
||||
<editor
|
||||
id="description"
|
||||
v-model="filter.description"
|
||||
:class="{ 'disabled': filterService.loading}"
|
||||
:disabled="filterService.loading"
|
||||
:preview-is-default="false"
|
||||
:placeholder="$t('filters.attributes.descriptionPlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="filters"
|
||||
>{{ $t('filters.title') }}</label>
|
||||
<div class="control">
|
||||
<Filters
|
||||
v-model="filters"
|
||||
:class="{ 'disabled': filterService.loading}"
|
||||
:disabled="filterService.loading"
|
||||
class="has-no-shadow has-no-border"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</create-edit>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,65 +1,77 @@
|
|||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
variant="hint-modal"
|
||||
>
|
||||
<card class="has-no-shadow" :title="$t('filters.create.title')">
|
||||
<p>
|
||||
{{ $t('filters.create.description') }}
|
||||
</p>
|
||||
<div class="field">
|
||||
<label class="label" for="title">{{ $t('filters.attributes.title') }}</label>
|
||||
<div class="control">
|
||||
<input
|
||||
v-model="savedFilter.title"
|
||||
:class="{ 'disabled': savedFilterService.loading}"
|
||||
:disabled="savedFilterService.loading || undefined"
|
||||
class="input"
|
||||
id="Title"
|
||||
:placeholder="$t('filters.attributes.titlePlaceholder')"
|
||||
type="text"
|
||||
v-focus
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="description">{{ $t('filters.attributes.description') }}</label>
|
||||
<div class="control">
|
||||
<editor
|
||||
:key="savedFilter.id"
|
||||
v-model="savedFilter.description"
|
||||
:class="{ 'disabled': savedFilterService.loading}"
|
||||
:disabled="savedFilterService.loading"
|
||||
:preview-is-default="false"
|
||||
id="description"
|
||||
:placeholder="$t('filters.attributes.descriptionPlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="filters">{{ $t('filters.title') }}</label>
|
||||
<div class="control">
|
||||
<Filters
|
||||
:class="{ 'disabled': savedFilterService.loading}"
|
||||
:disabled="savedFilterService.loading"
|
||||
class="has-no-shadow has-no-border"
|
||||
v-model="filters"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<modal
|
||||
variant="hint-modal"
|
||||
@close="$router.back()"
|
||||
>
|
||||
<card
|
||||
class="has-no-shadow"
|
||||
:title="$t('filters.create.title')"
|
||||
>
|
||||
<p>
|
||||
{{ $t('filters.create.description') }}
|
||||
</p>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="title"
|
||||
>{{ $t('filters.attributes.title') }}</label>
|
||||
<div class="control">
|
||||
<input
|
||||
id="Title"
|
||||
v-model="savedFilter.title"
|
||||
v-focus
|
||||
:class="{ 'disabled': savedFilterService.loading}"
|
||||
:disabled="savedFilterService.loading || undefined"
|
||||
class="input"
|
||||
:placeholder="$t('filters.attributes.titlePlaceholder')"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="description"
|
||||
>{{ $t('filters.attributes.description') }}</label>
|
||||
<div class="control">
|
||||
<editor
|
||||
id="description"
|
||||
:key="savedFilter.id"
|
||||
v-model="savedFilter.description"
|
||||
:class="{ 'disabled': savedFilterService.loading}"
|
||||
:disabled="savedFilterService.loading"
|
||||
:preview-is-default="false"
|
||||
:placeholder="$t('filters.attributes.descriptionPlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="filters"
|
||||
>{{ $t('filters.title') }}</label>
|
||||
<div class="control">
|
||||
<Filters
|
||||
v-model="filters"
|
||||
:class="{ 'disabled': savedFilterService.loading}"
|
||||
:disabled="savedFilterService.loading"
|
||||
class="has-no-shadow has-no-border"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<x-button
|
||||
:loading="savedFilterService.loading"
|
||||
:disabled="savedFilterService.loading"
|
||||
@click="create()"
|
||||
class="is-fullwidth"
|
||||
>
|
||||
{{ $t('filters.create.action') }}
|
||||
</x-button>
|
||||
</template>
|
||||
</card>
|
||||
</modal>
|
||||
<template #footer>
|
||||
<x-button
|
||||
:loading="savedFilterService.loading"
|
||||
:disabled="savedFilterService.loading"
|
||||
class="is-fullwidth"
|
||||
@click="create()"
|
||||
>
|
||||
{{ $t('filters.create.action') }}
|
||||
</x-button>
|
||||
</template>
|
||||
</card>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,112 +1,139 @@
|
|||
<template>
|
||||
<div :class="{ 'is-loading': loading}" class="loader-container">
|
||||
<x-button
|
||||
:to="{name:'labels.create'}"
|
||||
class="is-pulled-right"
|
||||
icon="plus"
|
||||
>
|
||||
{{ $t('label.create.header') }}
|
||||
</x-button>
|
||||
<div
|
||||
:class="{ 'is-loading': loading}"
|
||||
class="loader-container"
|
||||
>
|
||||
<x-button
|
||||
:to="{name:'labels.create'}"
|
||||
class="is-pulled-right"
|
||||
icon="plus"
|
||||
>
|
||||
{{ $t('label.create.header') }}
|
||||
</x-button>
|
||||
|
||||
<div class="content">
|
||||
<h1>{{ $t('label.manage') }}</h1>
|
||||
<p v-if="Object.entries(labels).length > 0">
|
||||
{{ $t('label.description') }}
|
||||
</p>
|
||||
<p v-else class="has-text-centered has-text-grey is-italic">
|
||||
{{ $t('label.newCTA') }}
|
||||
<router-link :to="{name:'labels.create'}">{{ $t('label.create.title') }}.</router-link>
|
||||
</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h1>{{ $t('label.manage') }}</h1>
|
||||
<p v-if="Object.entries(labels).length > 0">
|
||||
{{ $t('label.description') }}
|
||||
</p>
|
||||
<p
|
||||
v-else
|
||||
class="has-text-centered has-text-grey is-italic"
|
||||
>
|
||||
{{ $t('label.newCTA') }}
|
||||
<router-link :to="{name:'labels.create'}">
|
||||
{{ $t('label.create.title') }}.
|
||||
</router-link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="labels-list column">
|
||||
<span
|
||||
:class="{'disabled': userInfo.id !== l.createdBy.id}" :key="l.id"
|
||||
:style="{'background': l.hexColor, 'color': l.textColor}"
|
||||
class="tag"
|
||||
v-for="l in labels"
|
||||
>
|
||||
<span
|
||||
v-if="userInfo.id !== l.createdBy.id"
|
||||
v-tooltip.bottom="$t('label.edit.forbidden')">
|
||||
{{ l.title }}
|
||||
</span>
|
||||
<BaseButton
|
||||
:style="{'color': l.textColor}"
|
||||
@click="editLabel(l)"
|
||||
v-else>
|
||||
{{ l.title }}
|
||||
</BaseButton>
|
||||
<BaseButton @click="showDeleteDialoge(l)" class="delete is-small" v-if="userInfo.id === l.createdBy.id" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="column is-4" v-if="isLabelEdit">
|
||||
<card :title="$t('label.edit.header')" :has-close="true" @close="() => isLabelEdit = false">
|
||||
<form @submit.prevent="editLabelSubmit()">
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('label.attributes.title') }}</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
:placeholder="$t('label.attributes.titlePlaceholder')"
|
||||
type="text"
|
||||
v-model="labelEditLabel.title"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('label.attributes.description') }}</label>
|
||||
<div class="control">
|
||||
<editor
|
||||
:preview-is-default="false"
|
||||
:placeholder="$t('label.attributes.description')"
|
||||
v-if="editorActive"
|
||||
v-model="labelEditLabel.description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('label.attributes.color') }}</label>
|
||||
<div class="control">
|
||||
<color-picker v-model="labelEditLabel.hexColor"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<x-button
|
||||
:loading="loading"
|
||||
class="is-fullwidth"
|
||||
@click="editLabelSubmit()"
|
||||
>
|
||||
{{ $t('misc.save') }}
|
||||
</x-button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
@click="showDeleteDialoge(labelEditLabel)"
|
||||
icon="trash-alt"
|
||||
class="is-danger"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</card>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="labels-list column">
|
||||
<span
|
||||
v-for="l in labels"
|
||||
:key="l.id"
|
||||
:class="{'disabled': userInfo.id !== l.createdBy.id}"
|
||||
:style="{'background': l.hexColor, 'color': l.textColor}"
|
||||
class="tag"
|
||||
>
|
||||
<span
|
||||
v-if="userInfo.id !== l.createdBy.id"
|
||||
v-tooltip.bottom="$t('label.edit.forbidden')"
|
||||
>
|
||||
{{ l.title }}
|
||||
</span>
|
||||
<BaseButton
|
||||
v-else
|
||||
:style="{'color': l.textColor}"
|
||||
@click="editLabel(l)"
|
||||
>
|
||||
{{ l.title }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="userInfo.id === l.createdBy.id"
|
||||
class="delete is-small"
|
||||
@click="showDeleteDialoge(l)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="isLabelEdit"
|
||||
class="column is-4"
|
||||
>
|
||||
<card
|
||||
:title="$t('label.edit.header')"
|
||||
:has-close="true"
|
||||
@close="() => isLabelEdit = false"
|
||||
>
|
||||
<form @submit.prevent="editLabelSubmit()">
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('label.attributes.title') }}</label>
|
||||
<div class="control">
|
||||
<input
|
||||
v-model="labelEditLabel.title"
|
||||
class="input"
|
||||
:placeholder="$t('label.attributes.titlePlaceholder')"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('label.attributes.description') }}</label>
|
||||
<div class="control">
|
||||
<editor
|
||||
v-if="editorActive"
|
||||
v-model="labelEditLabel.description"
|
||||
:preview-is-default="false"
|
||||
:placeholder="$t('label.attributes.description')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('label.attributes.color') }}</label>
|
||||
<div class="control">
|
||||
<color-picker v-model="labelEditLabel.hexColor" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<x-button
|
||||
:loading="loading"
|
||||
class="is-fullwidth"
|
||||
@click="editLabelSubmit()"
|
||||
>
|
||||
{{ $t('misc.save') }}
|
||||
</x-button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
icon="trash-alt"
|
||||
class="is-danger"
|
||||
@click="showDeleteDialoge(labelEditLabel)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</card>
|
||||
</div>
|
||||
|
||||
<modal
|
||||
@close="showDeleteModal = false"
|
||||
@submit="deleteLabel(labelToDelete)"
|
||||
v-if="showDeleteModal"
|
||||
>
|
||||
<template #header><span>{{ $t('task.label.delete.header') }}</span></template>
|
||||
<modal
|
||||
v-if="showDeleteModal"
|
||||
@close="showDeleteModal = false"
|
||||
@submit="deleteLabel(labelToDelete)"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{ $t('task.label.delete.header') }}</span>
|
||||
</template>
|
||||
|
||||
<template #text>
|
||||
<p>{{ $t('task.label.delete.text1') }}<br/>
|
||||
{{ $t('task.label.delete.text2') }}</p>
|
||||
</template>
|
||||
</modal>
|
||||
</div>
|
||||
</div>
|
||||
<template #text>
|
||||
<p>
|
||||
{{ $t('task.label.delete.text1') }}<br>
|
||||
{{ $t('task.label.delete.text2') }}
|
||||
</p>
|
||||
</template>
|
||||
</modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,37 +1,43 @@
|
|||
<template>
|
||||
<create-edit
|
||||
:title="$t('label.create.title')"
|
||||
@create="newLabel()"
|
||||
:primary-disabled="label.title === ''"
|
||||
>
|
||||
<div class="field">
|
||||
<label class="label" for="labelTitle">{{ $t('label.attributes.title') }}</label>
|
||||
<div
|
||||
class="control is-expanded"
|
||||
:class="{ 'is-loading': loading }"
|
||||
>
|
||||
<input
|
||||
:class="{ disabled: loading }"
|
||||
class="input"
|
||||
:placeholder="$t('label.attributes.titlePlaceholder')"
|
||||
type="text"
|
||||
id="labelTitle"
|
||||
v-focus
|
||||
v-model="label.title"
|
||||
@keyup.enter="newLabel()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="showError && label.title === ''">
|
||||
{{ $t('label.create.titleRequired') }}
|
||||
</p>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('label.attributes.color') }}</label>
|
||||
<div class="control">
|
||||
<color-picker v-model="label.hexColor"/>
|
||||
</div>
|
||||
</div>
|
||||
</create-edit>
|
||||
<create-edit
|
||||
:title="$t('label.create.title')"
|
||||
:primary-disabled="label.title === ''"
|
||||
@create="newLabel()"
|
||||
>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="labelTitle"
|
||||
>{{ $t('label.attributes.title') }}</label>
|
||||
<div
|
||||
class="control is-expanded"
|
||||
:class="{ 'is-loading': loading }"
|
||||
>
|
||||
<input
|
||||
id="labelTitle"
|
||||
v-model="label.title"
|
||||
v-focus
|
||||
:class="{ disabled: loading }"
|
||||
class="input"
|
||||
:placeholder="$t('label.attributes.titlePlaceholder')"
|
||||
type="text"
|
||||
@keyup.enter="newLabel()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
v-if="showError && label.title === ''"
|
||||
class="help is-danger"
|
||||
>
|
||||
{{ $t('label.create.titleRequired') }}
|
||||
</p>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('label.attributes.color') }}</label>
|
||||
<div class="control">
|
||||
<color-picker v-model="label.hexColor" />
|
||||
</div>
|
||||
</div>
|
||||
</create-edit>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,67 +1,93 @@
|
|||
<template>
|
||||
<ListWrapper class="list-gantt" :list-id="props.listId" viewName="gantt">
|
||||
<template #header>
|
||||
<card class="gantt-options">
|
||||
<fancycheckbox class="is-block" v-model="showTaskswithoutDates">
|
||||
{{ $t('list.gantt.showTasksWithoutDates') }}
|
||||
</fancycheckbox>
|
||||
<div class="range-picker">
|
||||
<div class="field">
|
||||
<label class="label" for="dayWidth">{{ $t('list.gantt.size') }}</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select id="dayWidth" v-model.number="dayWidth">
|
||||
<option value="35">{{ $t('list.gantt.default') }}</option>
|
||||
<option value="10">{{ $t('list.gantt.month') }}</option>
|
||||
<option value="80">{{ $t('list.gantt.day') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="fromDate">{{ $t('list.gantt.from') }}</label>
|
||||
<div class="control">
|
||||
<flat-pickr
|
||||
:config="flatPickerConfig"
|
||||
class="input"
|
||||
id="fromDate"
|
||||
:placeholder="$t('list.gantt.from')"
|
||||
v-model="dateFrom"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="toDate">{{ $t('list.gantt.to') }}</label>
|
||||
<div class="control">
|
||||
<flat-pickr
|
||||
:config="flatPickerConfig"
|
||||
class="input"
|
||||
id="toDate"
|
||||
:placeholder="$t('list.gantt.to')"
|
||||
v-model="dateTo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</card>
|
||||
</template>
|
||||
<ListWrapper
|
||||
class="list-gantt"
|
||||
:list-id="props.listId"
|
||||
view-name="gantt"
|
||||
>
|
||||
<template #header>
|
||||
<card class="gantt-options">
|
||||
<fancycheckbox
|
||||
v-model="showTaskswithoutDates"
|
||||
class="is-block"
|
||||
>
|
||||
{{ $t('list.gantt.showTasksWithoutDates') }}
|
||||
</fancycheckbox>
|
||||
<div class="range-picker">
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="dayWidth"
|
||||
>{{ $t('list.gantt.size') }}</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select
|
||||
id="dayWidth"
|
||||
v-model.number="dayWidth"
|
||||
>
|
||||
<option value="35">
|
||||
{{ $t('list.gantt.default') }}
|
||||
</option>
|
||||
<option value="10">
|
||||
{{ $t('list.gantt.month') }}
|
||||
</option>
|
||||
<option value="80">
|
||||
{{ $t('list.gantt.day') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="fromDate"
|
||||
>{{ $t('list.gantt.from') }}</label>
|
||||
<div class="control">
|
||||
<flat-pickr
|
||||
id="fromDate"
|
||||
v-model="dateFrom"
|
||||
:config="flatPickerConfig"
|
||||
class="input"
|
||||
:placeholder="$t('list.gantt.from')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="toDate"
|
||||
>{{ $t('list.gantt.to') }}</label>
|
||||
<div class="control">
|
||||
<flat-pickr
|
||||
id="toDate"
|
||||
v-model="dateTo"
|
||||
:config="flatPickerConfig"
|
||||
class="input"
|
||||
:placeholder="$t('list.gantt.to')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<div class="gantt-chart-container">
|
||||
<card :padding="false" class="has-overflow">
|
||||
|
||||
<gantt-chart
|
||||
:date-from="dateFrom"
|
||||
:date-to="dateTo"
|
||||
:day-width="dayWidth"
|
||||
:list-id="props.listId"
|
||||
:show-taskswithout-dates="showTaskswithoutDates"
|
||||
/>
|
||||
|
||||
</card>
|
||||
</div>
|
||||
</template>
|
||||
</ListWrapper>
|
||||
<template #default>
|
||||
<div class="gantt-chart-container">
|
||||
<card
|
||||
:padding="false"
|
||||
class="has-overflow"
|
||||
>
|
||||
<gantt-chart
|
||||
:date-from="dateFrom"
|
||||
:date-to="dateTo"
|
||||
:day-width="dayWidth"
|
||||
:list-id="props.listId"
|
||||
:show-taskswithout-dates="showTaskswithoutDates"
|
||||
/>
|
||||
</card>
|
||||
</div>
|
||||
</template>
|
||||
</ListWrapper>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
>
|
||||
<card
|
||||
:title="list.title"
|
||||
>
|
||||
<div class="has-text-left" v-html="htmlDescription" v-if="htmlDescription !== ''"></div>
|
||||
<p v-else class="is-italic">
|
||||
{{ $t('list.noDescriptionAvailable') }}
|
||||
</p>
|
||||
</card>
|
||||
</modal>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
>
|
||||
<card
|
||||
:title="list.title"
|
||||
>
|
||||
<div
|
||||
v-if="htmlDescription !== ''"
|
||||
class="has-text-left"
|
||||
v-html="htmlDescription"
|
||||
/>
|
||||
<p
|
||||
v-else
|
||||
class="is-italic"
|
||||
>
|
||||
{{ $t('list.noDescriptionAvailable') }}
|
||||
</p>
|
||||
</card>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,226 +1,269 @@
|
|||
<template>
|
||||
<ListWrapper class="list-kanban" :list-id="listId" viewName="kanban">
|
||||
<template #header>
|
||||
<div class="filter-container" v-if="isSavedFilter(list)">
|
||||
<div class="items">
|
||||
<filter-popup
|
||||
v-model="params"
|
||||
@update:modelValue="loadBuckets"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<ListWrapper
|
||||
class="list-kanban"
|
||||
:list-id="listId"
|
||||
view-name="kanban"
|
||||
>
|
||||
<template #header>
|
||||
<div
|
||||
v-if="isSavedFilter(list)"
|
||||
class="filter-container"
|
||||
>
|
||||
<div class="items">
|
||||
<filter-popup
|
||||
v-model="params"
|
||||
@update:modelValue="loadBuckets"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<div class="kanban-view">
|
||||
<div
|
||||
:class="{ 'is-loading': loading && !oneTaskUpdating}"
|
||||
class="kanban kanban-bucket-container loader-container"
|
||||
>
|
||||
<draggable
|
||||
v-bind="dragOptions"
|
||||
:modelValue="buckets"
|
||||
@update:modelValue="updateBuckets"
|
||||
@end="updateBucketPosition"
|
||||
@start="() => dragBucket = true"
|
||||
group="buckets"
|
||||
:disabled="!canWrite"
|
||||
tag="ul"
|
||||
:item-key="({id}) => `bucket${id}`"
|
||||
:component-data="bucketDraggableComponentData"
|
||||
>
|
||||
<template #item="{element: bucket, index: bucketIndex }">
|
||||
<div
|
||||
class="bucket"
|
||||
:class="{'is-collapsed': collapsedBuckets[bucket.id]}"
|
||||
>
|
||||
<div class="bucket-header" @click="() => unCollapseBucket(bucket)">
|
||||
<span
|
||||
v-if="bucket.isDoneBucket"
|
||||
class="icon is-small has-text-success mr-2"
|
||||
v-tooltip="$t('list.kanban.doneBucketHint')"
|
||||
>
|
||||
<icon icon="check-double"/>
|
||||
</span>
|
||||
<h2
|
||||
@keydown.enter.prevent.stop="$event.target.blur()"
|
||||
@keydown.esc.prevent.stop="$event.target.blur()"
|
||||
@blur="saveBucketTitle(bucket.id, $event.target.textContent)"
|
||||
@click="focusBucketTitle"
|
||||
class="title input"
|
||||
:contenteditable="(bucketTitleEditable && canWrite && !collapsedBuckets[bucket.id]) ? true : undefined"
|
||||
:spellcheck="false">{{ bucket.title }}</h2>
|
||||
<span
|
||||
:class="{'is-max': bucket.tasks.length >= bucket.limit}"
|
||||
class="limit"
|
||||
v-if="bucket.limit > 0">
|
||||
{{ bucket.tasks.length }}/{{ bucket.limit }}
|
||||
</span>
|
||||
<dropdown
|
||||
class="is-right options"
|
||||
v-if="canWrite && !collapsedBuckets[bucket.id]"
|
||||
trigger-icon="ellipsis-v"
|
||||
@close="() => showSetLimitInput = false"
|
||||
>
|
||||
<dropdown-item
|
||||
@click.stop="showSetLimitInput = true"
|
||||
>
|
||||
<div class="field has-addons" v-if="showSetLimitInput">
|
||||
<div class="control">
|
||||
<input
|
||||
@keyup.esc="() => showSetLimitInput = false"
|
||||
@keyup.enter="() => showSetLimitInput = false"
|
||||
:value="bucket.limit"
|
||||
@input="(event) => setBucketLimit(bucket.id, parseInt(event.target.value))"
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
v-focus.always
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
:disabled="bucket.limit < 0"
|
||||
:icon="['far', 'save']"
|
||||
:shadow="false"
|
||||
v-cy="'setBucketLimit'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
{{
|
||||
$t('list.kanban.limit', {limit: bucket.limit > 0 ? bucket.limit : $t('list.kanban.noLimit')})
|
||||
}}
|
||||
</template>
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
@click.stop="toggleDoneBucket(bucket)"
|
||||
v-tooltip="$t('list.kanban.doneBucketHintExtended')"
|
||||
>
|
||||
<span class="icon is-small" :class="{'has-text-success': bucket.isDoneBucket}">
|
||||
<icon icon="check-double"/>
|
||||
</span>
|
||||
{{ $t('list.kanban.doneBucket') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
@click.stop="() => collapseBucket(bucket)"
|
||||
>
|
||||
{{ $t('list.kanban.collapse') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:class="{'is-disabled': buckets.length <= 1}"
|
||||
@click.stop="() => deleteBucketModal(bucket.id)"
|
||||
class="has-text-danger"
|
||||
v-tooltip="buckets.length <= 1 ? $t('list.kanban.deleteLast') : ''"
|
||||
>
|
||||
<span class="icon is-small">
|
||||
<icon icon="trash-alt"/>
|
||||
</span>
|
||||
{{ $t('misc.delete') }}
|
||||
</dropdown-item>
|
||||
</dropdown>
|
||||
</div>
|
||||
<template #default>
|
||||
<div class="kanban-view">
|
||||
<div
|
||||
:class="{ 'is-loading': loading && !oneTaskUpdating}"
|
||||
class="kanban kanban-bucket-container loader-container"
|
||||
>
|
||||
<draggable
|
||||
v-bind="dragOptions"
|
||||
:model-value="buckets"
|
||||
group="buckets"
|
||||
:disabled="!canWrite"
|
||||
tag="ul"
|
||||
:item-key="({id}) => `bucket${id}`"
|
||||
:component-data="bucketDraggableComponentData"
|
||||
@update:modelValue="updateBuckets"
|
||||
@end="updateBucketPosition"
|
||||
@start="() => dragBucket = true"
|
||||
>
|
||||
<template #item="{element: bucket, index: bucketIndex }">
|
||||
<div
|
||||
class="bucket"
|
||||
:class="{'is-collapsed': collapsedBuckets[bucket.id]}"
|
||||
>
|
||||
<div
|
||||
class="bucket-header"
|
||||
@click="() => unCollapseBucket(bucket)"
|
||||
>
|
||||
<span
|
||||
v-if="bucket.isDoneBucket"
|
||||
v-tooltip="$t('list.kanban.doneBucketHint')"
|
||||
class="icon is-small has-text-success mr-2"
|
||||
>
|
||||
<icon icon="check-double" />
|
||||
</span>
|
||||
<h2
|
||||
class="title input"
|
||||
:contenteditable="(bucketTitleEditable && canWrite && !collapsedBuckets[bucket.id]) ? true : undefined"
|
||||
:spellcheck="false"
|
||||
@keydown.enter.prevent.stop="$event.target.blur()"
|
||||
@keydown.esc.prevent.stop="$event.target.blur()"
|
||||
@blur="saveBucketTitle(bucket.id, $event.target.textContent)"
|
||||
@click="focusBucketTitle"
|
||||
>
|
||||
{{ bucket.title }}
|
||||
</h2>
|
||||
<span
|
||||
v-if="bucket.limit > 0"
|
||||
:class="{'is-max': bucket.tasks.length >= bucket.limit}"
|
||||
class="limit"
|
||||
>
|
||||
{{ bucket.tasks.length }}/{{ bucket.limit }}
|
||||
</span>
|
||||
<dropdown
|
||||
v-if="canWrite && !collapsedBuckets[bucket.id]"
|
||||
class="is-right options"
|
||||
trigger-icon="ellipsis-v"
|
||||
@close="() => showSetLimitInput = false"
|
||||
>
|
||||
<dropdown-item
|
||||
@click.stop="showSetLimitInput = true"
|
||||
>
|
||||
<div
|
||||
v-if="showSetLimitInput"
|
||||
class="field has-addons"
|
||||
>
|
||||
<div class="control">
|
||||
<input
|
||||
v-focus.always
|
||||
:value="bucket.limit"
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@keyup.esc="() => showSetLimitInput = false"
|
||||
@keyup.enter="() => showSetLimitInput = false"
|
||||
@input="(event) => setBucketLimit(bucket.id, parseInt(event.target.value))"
|
||||
>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
v-cy="'setBucketLimit'"
|
||||
:disabled="bucket.limit < 0"
|
||||
:icon="['far', 'save']"
|
||||
:shadow="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
{{
|
||||
$t('list.kanban.limit', {limit: bucket.limit > 0 ? bucket.limit : $t('list.kanban.noLimit')})
|
||||
}}
|
||||
</template>
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
v-tooltip="$t('list.kanban.doneBucketHintExtended')"
|
||||
@click.stop="toggleDoneBucket(bucket)"
|
||||
>
|
||||
<span
|
||||
class="icon is-small"
|
||||
:class="{'has-text-success': bucket.isDoneBucket}"
|
||||
>
|
||||
<icon icon="check-double" />
|
||||
</span>
|
||||
{{ $t('list.kanban.doneBucket') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
@click.stop="() => collapseBucket(bucket)"
|
||||
>
|
||||
{{ $t('list.kanban.collapse') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
v-tooltip="buckets.length <= 1 ? $t('list.kanban.deleteLast') : ''"
|
||||
:class="{'is-disabled': buckets.length <= 1}"
|
||||
class="has-text-danger"
|
||||
@click.stop="() => deleteBucketModal(bucket.id)"
|
||||
>
|
||||
<span class="icon is-small">
|
||||
<icon icon="trash-alt" />
|
||||
</span>
|
||||
{{ $t('misc.delete') }}
|
||||
</dropdown-item>
|
||||
</dropdown>
|
||||
</div>
|
||||
|
||||
<draggable
|
||||
v-bind="dragOptions"
|
||||
:modelValue="bucket.tasks"
|
||||
@update:modelValue="(tasks) => updateTasks(bucket.id, tasks)"
|
||||
@start="() => dragstart(bucket)"
|
||||
@end="updateTaskPosition"
|
||||
:group="{name: 'tasks', put: shouldAcceptDrop(bucket) && !dragBucket}"
|
||||
:disabled="!canWrite"
|
||||
:data-bucket-index="bucketIndex"
|
||||
tag="ul"
|
||||
:item-key="(task) => `bucket${bucket.id}-task${task.id}`"
|
||||
:component-data="getTaskDraggableTaskComponentData(bucket)"
|
||||
>
|
||||
<template #footer>
|
||||
<div class="bucket-footer" v-if="canWrite">
|
||||
<div class="field" v-if="showNewTaskInput[bucket.id]">
|
||||
<div class="control" :class="{'is-loading': loading || taskLoading}">
|
||||
<input
|
||||
class="input"
|
||||
:disabled="loading || taskLoading || undefined"
|
||||
@focusout="toggleShowNewTaskInput(bucket.id)"
|
||||
@keyup.enter="addTaskToBucket(bucket.id)"
|
||||
@keyup.esc="toggleShowNewTaskInput(bucket.id)"
|
||||
:placeholder="$t('list.kanban.addTaskPlaceholder')"
|
||||
type="text"
|
||||
v-focus.always
|
||||
v-model="newTaskText"
|
||||
/>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="newTaskError[bucket.id] && newTaskText === ''">
|
||||
{{ $t('list.create.addTitleRequired') }}
|
||||
</p>
|
||||
</div>
|
||||
<x-button
|
||||
@click="toggleShowNewTaskInput(bucket.id)"
|
||||
class="is-fullwidth has-text-centered"
|
||||
:shadow="false"
|
||||
v-else
|
||||
icon="plus"
|
||||
variant="secondary"
|
||||
>
|
||||
{{ bucket.tasks.length === 0 ? $t('list.kanban.addTask') : $t('list.kanban.addAnotherTask') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</template>
|
||||
<draggable
|
||||
v-bind="dragOptions"
|
||||
:model-value="bucket.tasks"
|
||||
:group="{name: 'tasks', put: shouldAcceptDrop(bucket) && !dragBucket}"
|
||||
:disabled="!canWrite"
|
||||
:data-bucket-index="bucketIndex"
|
||||
tag="ul"
|
||||
:item-key="(task) => `bucket${bucket.id}-task${task.id}`"
|
||||
:component-data="getTaskDraggableTaskComponentData(bucket)"
|
||||
@update:modelValue="(tasks) => updateTasks(bucket.id, tasks)"
|
||||
@start="() => dragstart(bucket)"
|
||||
@end="updateTaskPosition"
|
||||
>
|
||||
<template #footer>
|
||||
<div
|
||||
v-if="canWrite"
|
||||
class="bucket-footer"
|
||||
>
|
||||
<div
|
||||
v-if="showNewTaskInput[bucket.id]"
|
||||
class="field"
|
||||
>
|
||||
<div
|
||||
class="control"
|
||||
:class="{'is-loading': loading || taskLoading}"
|
||||
>
|
||||
<input
|
||||
v-model="newTaskText"
|
||||
v-focus.always
|
||||
class="input"
|
||||
:disabled="loading || taskLoading || undefined"
|
||||
:placeholder="$t('list.kanban.addTaskPlaceholder')"
|
||||
type="text"
|
||||
@focusout="toggleShowNewTaskInput(bucket.id)"
|
||||
@keyup.enter="addTaskToBucket(bucket.id)"
|
||||
@keyup.esc="toggleShowNewTaskInput(bucket.id)"
|
||||
>
|
||||
</div>
|
||||
<p
|
||||
v-if="newTaskError[bucket.id] && newTaskText === ''"
|
||||
class="help is-danger"
|
||||
>
|
||||
{{ $t('list.create.addTitleRequired') }}
|
||||
</p>
|
||||
</div>
|
||||
<x-button
|
||||
v-else
|
||||
class="is-fullwidth has-text-centered"
|
||||
:shadow="false"
|
||||
icon="plus"
|
||||
variant="secondary"
|
||||
@click="toggleShowNewTaskInput(bucket.id)"
|
||||
>
|
||||
{{ bucket.tasks.length === 0 ? $t('list.kanban.addTask') : $t('list.kanban.addAnotherTask') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item="{element: task}">
|
||||
<div class="task-item">
|
||||
<kanban-card class="kanban-card" :task="task" :loading="taskUpdating[task.id] ?? false"/>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<template #item="{element: task}">
|
||||
<div class="task-item">
|
||||
<kanban-card
|
||||
class="kanban-card"
|
||||
:task="task"
|
||||
:loading="taskUpdating[task.id] ?? false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<div class="bucket new-bucket" v-if="canWrite && !loading && buckets.length > 0">
|
||||
<input
|
||||
:class="{'is-loading': loading}"
|
||||
:disabled="loading || undefined"
|
||||
@blur="() => showNewBucketInput = false"
|
||||
@keyup.enter="createNewBucket"
|
||||
@keyup.esc="$event.target.blur()"
|
||||
class="input"
|
||||
:placeholder="$t('list.kanban.addBucketPlaceholder')"
|
||||
type="text"
|
||||
v-focus.always
|
||||
v-if="showNewBucketInput"
|
||||
v-model="newBucketTitle"
|
||||
/>
|
||||
<x-button
|
||||
v-else
|
||||
@click="() => showNewBucketInput = true"
|
||||
:shadow="false"
|
||||
class="is-transparent is-fullwidth has-text-centered"
|
||||
variant="secondary"
|
||||
icon="plus"
|
||||
>
|
||||
{{ $t('list.kanban.addBucket') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="canWrite && !loading && buckets.length > 0"
|
||||
class="bucket new-bucket"
|
||||
>
|
||||
<input
|
||||
v-if="showNewBucketInput"
|
||||
v-model="newBucketTitle"
|
||||
v-focus.always
|
||||
:class="{'is-loading': loading}"
|
||||
:disabled="loading || undefined"
|
||||
class="input"
|
||||
:placeholder="$t('list.kanban.addBucketPlaceholder')"
|
||||
type="text"
|
||||
@blur="() => showNewBucketInput = false"
|
||||
@keyup.enter="createNewBucket"
|
||||
@keyup.esc="$event.target.blur()"
|
||||
>
|
||||
<x-button
|
||||
v-else
|
||||
:shadow="false"
|
||||
class="is-transparent is-fullwidth has-text-centered"
|
||||
variant="secondary"
|
||||
icon="plus"
|
||||
@click="() => showNewBucketInput = true"
|
||||
>
|
||||
{{ $t('list.kanban.addBucket') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<transition name="modal">
|
||||
<modal
|
||||
v-if="showBucketDeleteModal"
|
||||
@close="showBucketDeleteModal = false"
|
||||
@submit="deleteBucket()"
|
||||
>
|
||||
<template #header><span>{{ $t('list.kanban.deleteHeaderBucket') }}</span></template>
|
||||
<transition name="modal">
|
||||
<modal
|
||||
v-if="showBucketDeleteModal"
|
||||
@close="showBucketDeleteModal = false"
|
||||
@submit="deleteBucket()"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{ $t('list.kanban.deleteHeaderBucket') }}</span>
|
||||
</template>
|
||||
|
||||
<template #text>
|
||||
<p>{{ $t('list.kanban.deleteBucketText1') }}<br/>
|
||||
{{ $t('list.kanban.deleteBucketText2') }}</p>
|
||||
</template>
|
||||
</modal>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
</ListWrapper>
|
||||
<template #text>
|
||||
<p>
|
||||
{{ $t('list.kanban.deleteBucketText1') }}<br>
|
||||
{{ $t('list.kanban.deleteBucketText2') }}
|
||||
</p>
|
||||
</template>
|
||||
</modal>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
</ListWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -1,137 +1,151 @@
|
|||
<template>
|
||||
<ListWrapper class="list-list" :list-id="listId" viewName="list">
|
||||
<template #header>
|
||||
<div
|
||||
class="filter-container"
|
||||
v-if="!isSavedFilter(list)"
|
||||
>
|
||||
<div class="items">
|
||||
<div class="search">
|
||||
<div :class="{ hidden: !showTaskSearch }" class="field has-addons">
|
||||
<div class="control has-icons-left has-icons-right">
|
||||
<input
|
||||
@blur="hideSearchBar()"
|
||||
@keyup.enter="searchTasks"
|
||||
class="input"
|
||||
:placeholder="$t('misc.search')"
|
||||
type="text"
|
||||
v-focus
|
||||
v-model="searchTerm"
|
||||
/>
|
||||
<span class="icon is-left">
|
||||
<icon icon="search"/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
:loading="loading"
|
||||
@click="searchTasks"
|
||||
:shadow="false"
|
||||
>
|
||||
{{ $t('misc.search') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
<x-button
|
||||
@click="showTaskSearch = !showTaskSearch"
|
||||
icon="search"
|
||||
variant="secondary"
|
||||
v-if="!showTaskSearch"
|
||||
/>
|
||||
</div>
|
||||
<filter-popup
|
||||
v-model="params"
|
||||
@update:modelValue="loadTasks()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<ListWrapper
|
||||
class="list-list"
|
||||
:list-id="listId"
|
||||
view-name="list"
|
||||
>
|
||||
<template #header>
|
||||
<div
|
||||
v-if="!isSavedFilter(list)"
|
||||
class="filter-container"
|
||||
>
|
||||
<div class="items">
|
||||
<div class="search">
|
||||
<div
|
||||
:class="{ hidden: !showTaskSearch }"
|
||||
class="field has-addons"
|
||||
>
|
||||
<div class="control has-icons-left has-icons-right">
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
v-focus
|
||||
class="input"
|
||||
:placeholder="$t('misc.search')"
|
||||
type="text"
|
||||
@blur="hideSearchBar()"
|
||||
@keyup.enter="searchTasks"
|
||||
>
|
||||
<span class="icon is-left">
|
||||
<icon icon="search" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
:loading="loading"
|
||||
:shadow="false"
|
||||
@click="searchTasks"
|
||||
>
|
||||
{{ $t('misc.search') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
<x-button
|
||||
v-if="!showTaskSearch"
|
||||
icon="search"
|
||||
variant="secondary"
|
||||
@click="showTaskSearch = !showTaskSearch"
|
||||
/>
|
||||
</div>
|
||||
<filter-popup
|
||||
v-model="params"
|
||||
@update:modelValue="loadTasks()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<div
|
||||
:class="{ 'is-loading': loading }"
|
||||
class="loader-container is-max-width-desktop list-view"
|
||||
>
|
||||
<card :padding="false" :has-content="false" class="has-overflow">
|
||||
<template
|
||||
v-if="!list.isArchived && canWrite"
|
||||
>
|
||||
<add-task
|
||||
@taskAdded="updateTaskList"
|
||||
ref="addTaskRef"
|
||||
:default-position="firstNewPosition"
|
||||
/>
|
||||
</template>
|
||||
<template #default>
|
||||
<div
|
||||
:class="{ 'is-loading': loading }"
|
||||
class="loader-container is-max-width-desktop list-view"
|
||||
>
|
||||
<card
|
||||
:padding="false"
|
||||
:has-content="false"
|
||||
class="has-overflow"
|
||||
>
|
||||
<template
|
||||
v-if="!list.isArchived && canWrite"
|
||||
>
|
||||
<add-task
|
||||
ref="addTaskRef"
|
||||
:default-position="firstNewPosition"
|
||||
@taskAdded="updateTaskList"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<nothing v-if="ctaVisible && tasks.length === 0 && !loading">
|
||||
{{ $t('list.list.empty') }}
|
||||
<ButtonLink @click="focusNewTaskInput()">
|
||||
{{ $t('list.list.newTaskCta') }}
|
||||
</ButtonLink>
|
||||
</nothing>
|
||||
<nothing v-if="ctaVisible && tasks.length === 0 && !loading">
|
||||
{{ $t('list.list.empty') }}
|
||||
<ButtonLink @click="focusNewTaskInput()">
|
||||
{{ $t('list.list.newTaskCta') }}
|
||||
</ButtonLink>
|
||||
</nothing>
|
||||
|
||||
<div class="tasks-container" :class="{ 'has-task-edit-open': isTaskEdit }">
|
||||
<div
|
||||
class="tasks mt-0"
|
||||
v-if="tasks && tasks.length > 0"
|
||||
>
|
||||
<draggable
|
||||
v-bind="DRAG_OPTIONS"
|
||||
v-model="tasks"
|
||||
group="tasks"
|
||||
@start="() => drag = true"
|
||||
@end="saveTaskPosition"
|
||||
handle=".handle"
|
||||
:disabled="!canWrite"
|
||||
item-key="id"
|
||||
tag="ul"
|
||||
:component-data="{
|
||||
class: { 'dragging-disabled': !canWrite || isAlphabeticalSorting },
|
||||
type: 'transition-group'
|
||||
}"
|
||||
>
|
||||
<template #item="{element: t}">
|
||||
<single-task-in-list
|
||||
:show-list-color="false"
|
||||
:disabled="!canWrite"
|
||||
:can-mark-as-done="canWrite || isSavedFilter(list)"
|
||||
:the-task="t"
|
||||
@taskUpdated="updateTasks"
|
||||
>
|
||||
<template v-if="canWrite">
|
||||
<span class="icon handle">
|
||||
<icon icon="grip-lines"/>
|
||||
</span>
|
||||
<BaseButton
|
||||
@click="editTask(t.id)"
|
||||
class="icon settings"
|
||||
v-if="!list.isArchived"
|
||||
>
|
||||
<icon icon="pencil-alt"/>
|
||||
</BaseButton>
|
||||
</template>
|
||||
</single-task-in-list>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
<EditTask
|
||||
v-if="isTaskEdit"
|
||||
class="taskedit mt-0"
|
||||
:title="$t('list.list.editTask')"
|
||||
@close="closeTaskEditPane()"
|
||||
:shadow="false"
|
||||
:task="taskEditTask"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="tasks-container"
|
||||
:class="{ 'has-task-edit-open': isTaskEdit }"
|
||||
>
|
||||
<div
|
||||
v-if="tasks && tasks.length > 0"
|
||||
class="tasks mt-0"
|
||||
>
|
||||
<draggable
|
||||
v-bind="DRAG_OPTIONS"
|
||||
v-model="tasks"
|
||||
group="tasks"
|
||||
handle=".handle"
|
||||
:disabled="!canWrite"
|
||||
item-key="id"
|
||||
tag="ul"
|
||||
:component-data="{
|
||||
class: { 'dragging-disabled': !canWrite || isAlphabeticalSorting },
|
||||
type: 'transition-group'
|
||||
}"
|
||||
@start="() => drag = true"
|
||||
@end="saveTaskPosition"
|
||||
>
|
||||
<template #item="{element: t}">
|
||||
<single-task-in-list
|
||||
:show-list-color="false"
|
||||
:disabled="!canWrite"
|
||||
:can-mark-as-done="canWrite || isSavedFilter(list)"
|
||||
:the-task="t"
|
||||
@taskUpdated="updateTasks"
|
||||
>
|
||||
<template v-if="canWrite">
|
||||
<span class="icon handle">
|
||||
<icon icon="grip-lines" />
|
||||
</span>
|
||||
<BaseButton
|
||||
v-if="!list.isArchived"
|
||||
class="icon settings"
|
||||
@click="editTask(t.id)"
|
||||
>
|
||||
<icon icon="pencil-alt" />
|
||||
</BaseButton>
|
||||
</template>
|
||||
</single-task-in-list>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
<EditTask
|
||||
v-if="isTaskEdit"
|
||||
class="taskedit mt-0"
|
||||
:title="$t('list.list.editTask')"
|
||||
:shadow="false"
|
||||
:task="taskEditTask"
|
||||
@close="closeTaskEditPane()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Pagination
|
||||
:total-pages="totalPages"
|
||||
:current-page="currentPage"
|
||||
/>
|
||||
</card>
|
||||
</div>
|
||||
</template>
|
||||
</ListWrapper>
|
||||
<Pagination
|
||||
:total-pages="totalPages"
|
||||
:current-page="currentPage"
|
||||
/>
|
||||
</card>
|
||||
</div>
|
||||
</template>
|
||||
</ListWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -1,182 +1,257 @@
|
|||
<template>
|
||||
<ListWrapper class="list-table" :list-id="listId" viewName="table">
|
||||
<template #header>
|
||||
<div class="filter-container">
|
||||
<div class="items">
|
||||
<popup>
|
||||
<template #trigger="{toggle}">
|
||||
<x-button
|
||||
@click.prevent.stop="toggle()"
|
||||
icon="th"
|
||||
variant="secondary"
|
||||
>
|
||||
{{ $t('list.table.columns') }}
|
||||
</x-button>
|
||||
</template>
|
||||
<template #content="{isOpen}">
|
||||
<card class="columns-filter" :class="{'is-open': isOpen}">
|
||||
<fancycheckbox v-model="activeColumns.id">#</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.done">
|
||||
{{ $t('task.attributes.done') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.title">
|
||||
{{ $t('task.attributes.title') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.priority">
|
||||
{{ $t('task.attributes.priority') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.labels">
|
||||
{{ $t('task.attributes.labels') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.assignees">
|
||||
{{ $t('task.attributes.assignees') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.dueDate">
|
||||
{{ $t('task.attributes.dueDate') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.startDate">
|
||||
{{ $t('task.attributes.startDate') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.endDate">
|
||||
{{ $t('task.attributes.endDate') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.percentDone">
|
||||
{{ $t('task.attributes.percentDone') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.created">
|
||||
{{ $t('task.attributes.created') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.updated">
|
||||
{{ $t('task.attributes.updated') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.createdBy">
|
||||
{{ $t('task.attributes.createdBy') }}
|
||||
</fancycheckbox>
|
||||
</card>
|
||||
</template>
|
||||
</popup>
|
||||
<filter-popup v-model="params"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<ListWrapper
|
||||
class="list-table"
|
||||
:list-id="listId"
|
||||
view-name="table"
|
||||
>
|
||||
<template #header>
|
||||
<div class="filter-container">
|
||||
<div class="items">
|
||||
<popup>
|
||||
<template #trigger="{toggle}">
|
||||
<x-button
|
||||
icon="th"
|
||||
variant="secondary"
|
||||
@click.prevent.stop="toggle()"
|
||||
>
|
||||
{{ $t('list.table.columns') }}
|
||||
</x-button>
|
||||
</template>
|
||||
<template #content="{isOpen}">
|
||||
<card
|
||||
class="columns-filter"
|
||||
:class="{'is-open': isOpen}"
|
||||
>
|
||||
<fancycheckbox v-model="activeColumns.id">
|
||||
#
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.done">
|
||||
{{ $t('task.attributes.done') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.title">
|
||||
{{ $t('task.attributes.title') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.priority">
|
||||
{{ $t('task.attributes.priority') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.labels">
|
||||
{{ $t('task.attributes.labels') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.assignees">
|
||||
{{ $t('task.attributes.assignees') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.dueDate">
|
||||
{{ $t('task.attributes.dueDate') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.startDate">
|
||||
{{ $t('task.attributes.startDate') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.endDate">
|
||||
{{ $t('task.attributes.endDate') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.percentDone">
|
||||
{{ $t('task.attributes.percentDone') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.created">
|
||||
{{ $t('task.attributes.created') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.updated">
|
||||
{{ $t('task.attributes.updated') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.createdBy">
|
||||
{{ $t('task.attributes.createdBy') }}
|
||||
</fancycheckbox>
|
||||
</card>
|
||||
</template>
|
||||
</popup>
|
||||
<filter-popup v-model="params" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<div :class="{'is-loading': loading}" class="loader-container">
|
||||
<card :padding="false" :has-content="false">
|
||||
<div class="has-horizontal-overflow">
|
||||
<table class="table has-actions is-hoverable is-fullwidth mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-if="activeColumns.id">
|
||||
#
|
||||
<Sort :order="sortBy.id" @click="sort('id')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.done">
|
||||
{{ $t('task.attributes.done') }}
|
||||
<Sort :order="sortBy.done" @click="sort('done')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.title">
|
||||
{{ $t('task.attributes.title') }}
|
||||
<Sort :order="sortBy.title" @click="sort('title')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.priority">
|
||||
{{ $t('task.attributes.priority') }}
|
||||
<Sort :order="sortBy.priority" @click="sort('priority')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.labels">
|
||||
{{ $t('task.attributes.labels') }}
|
||||
</th>
|
||||
<th v-if="activeColumns.assignees">
|
||||
{{ $t('task.attributes.assignees') }}
|
||||
</th>
|
||||
<th v-if="activeColumns.dueDate">
|
||||
{{ $t('task.attributes.dueDate') }}
|
||||
<Sort :order="sortBy.due_date" @click="sort('due_date')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.startDate">
|
||||
{{ $t('task.attributes.startDate') }}
|
||||
<Sort :order="sortBy.start_date" @click="sort('start_date')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.endDate">
|
||||
{{ $t('task.attributes.endDate') }}
|
||||
<Sort :order="sortBy.end_date" @click="sort('end_date')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.percentDone">
|
||||
{{ $t('task.attributes.percentDone') }}
|
||||
<Sort :order="sortBy.percent_done" @click="sort('percent_done')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.created">
|
||||
{{ $t('task.attributes.created') }}
|
||||
<Sort :order="sortBy.created" @click="sort('created')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.updated">
|
||||
{{ $t('task.attributes.updated') }}
|
||||
<Sort :order="sortBy.updated" @click="sort('updated')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.createdBy">
|
||||
{{ $t('task.attributes.createdBy') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr :key="t.id" v-for="t in tasks">
|
||||
<td v-if="activeColumns.id">
|
||||
<router-link :to="taskDetailRoutes[t.id]">
|
||||
<template v-if="t.identifier === ''">
|
||||
#{{ t.index }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t.identifier }}
|
||||
</template>
|
||||
</router-link>
|
||||
</td>
|
||||
<td v-if="activeColumns.done">
|
||||
<Done :is-done="t.done" variant="small"/>
|
||||
</td>
|
||||
<td v-if="activeColumns.title">
|
||||
<router-link :to="taskDetailRoutes[t.id]">{{ t.title }}</router-link>
|
||||
</td>
|
||||
<td v-if="activeColumns.priority">
|
||||
<priority-label :priority="t.priority" :done="t.done" :show-all="true"/>
|
||||
</td>
|
||||
<td v-if="activeColumns.labels">
|
||||
<labels :labels="t.labels"/>
|
||||
</td>
|
||||
<td v-if="activeColumns.assignees">
|
||||
<user
|
||||
:avatar-size="27"
|
||||
:is-inline="true"
|
||||
:key="t.id + 'assignee' + a.id + i"
|
||||
:show-username="false"
|
||||
:user="a"
|
||||
v-for="(a, i) in t.assignees"
|
||||
/>
|
||||
</td>
|
||||
<date-table-cell :date="t.dueDate" v-if="activeColumns.dueDate"/>
|
||||
<date-table-cell :date="t.startDate" v-if="activeColumns.startDate"/>
|
||||
<date-table-cell :date="t.endDate" v-if="activeColumns.endDate"/>
|
||||
<td v-if="activeColumns.percentDone">{{ t.percentDone * 100 }}%</td>
|
||||
<date-table-cell :date="t.created" v-if="activeColumns.created"/>
|
||||
<date-table-cell :date="t.updated" v-if="activeColumns.updated"/>
|
||||
<td v-if="activeColumns.createdBy">
|
||||
<user
|
||||
:avatar-size="27"
|
||||
:show-username="false"
|
||||
:user="t.createdBy"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<template #default>
|
||||
<div
|
||||
:class="{'is-loading': loading}"
|
||||
class="loader-container"
|
||||
>
|
||||
<card
|
||||
:padding="false"
|
||||
:has-content="false"
|
||||
>
|
||||
<div class="has-horizontal-overflow">
|
||||
<table class="table has-actions is-hoverable is-fullwidth mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-if="activeColumns.id">
|
||||
#
|
||||
<Sort
|
||||
:order="sortBy.id"
|
||||
@click="sort('id')"
|
||||
/>
|
||||
</th>
|
||||
<th v-if="activeColumns.done">
|
||||
{{ $t('task.attributes.done') }}
|
||||
<Sort
|
||||
:order="sortBy.done"
|
||||
@click="sort('done')"
|
||||
/>
|
||||
</th>
|
||||
<th v-if="activeColumns.title">
|
||||
{{ $t('task.attributes.title') }}
|
||||
<Sort
|
||||
:order="sortBy.title"
|
||||
@click="sort('title')"
|
||||
/>
|
||||
</th>
|
||||
<th v-if="activeColumns.priority">
|
||||
{{ $t('task.attributes.priority') }}
|
||||
<Sort
|
||||
:order="sortBy.priority"
|
||||
@click="sort('priority')"
|
||||
/>
|
||||
</th>
|
||||
<th v-if="activeColumns.labels">
|
||||
{{ $t('task.attributes.labels') }}
|
||||
</th>
|
||||
<th v-if="activeColumns.assignees">
|
||||
{{ $t('task.attributes.assignees') }}
|
||||
</th>
|
||||
<th v-if="activeColumns.dueDate">
|
||||
{{ $t('task.attributes.dueDate') }}
|
||||
<Sort
|
||||
:order="sortBy.due_date"
|
||||
@click="sort('due_date')"
|
||||
/>
|
||||
</th>
|
||||
<th v-if="activeColumns.startDate">
|
||||
{{ $t('task.attributes.startDate') }}
|
||||
<Sort
|
||||
:order="sortBy.start_date"
|
||||
@click="sort('start_date')"
|
||||
/>
|
||||
</th>
|
||||
<th v-if="activeColumns.endDate">
|
||||
{{ $t('task.attributes.endDate') }}
|
||||
<Sort
|
||||
:order="sortBy.end_date"
|
||||
@click="sort('end_date')"
|
||||
/>
|
||||
</th>
|
||||
<th v-if="activeColumns.percentDone">
|
||||
{{ $t('task.attributes.percentDone') }}
|
||||
<Sort
|
||||
:order="sortBy.percent_done"
|
||||
@click="sort('percent_done')"
|
||||
/>
|
||||
</th>
|
||||
<th v-if="activeColumns.created">
|
||||
{{ $t('task.attributes.created') }}
|
||||
<Sort
|
||||
:order="sortBy.created"
|
||||
@click="sort('created')"
|
||||
/>
|
||||
</th>
|
||||
<th v-if="activeColumns.updated">
|
||||
{{ $t('task.attributes.updated') }}
|
||||
<Sort
|
||||
:order="sortBy.updated"
|
||||
@click="sort('updated')"
|
||||
/>
|
||||
</th>
|
||||
<th v-if="activeColumns.createdBy">
|
||||
{{ $t('task.attributes.createdBy') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="t in tasks"
|
||||
:key="t.id"
|
||||
>
|
||||
<td v-if="activeColumns.id">
|
||||
<router-link :to="taskDetailRoutes[t.id]">
|
||||
<template v-if="t.identifier === ''">
|
||||
#{{ t.index }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t.identifier }}
|
||||
</template>
|
||||
</router-link>
|
||||
</td>
|
||||
<td v-if="activeColumns.done">
|
||||
<Done
|
||||
:is-done="t.done"
|
||||
variant="small"
|
||||
/>
|
||||
</td>
|
||||
<td v-if="activeColumns.title">
|
||||
<router-link :to="taskDetailRoutes[t.id]">
|
||||
{{ t.title }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td v-if="activeColumns.priority">
|
||||
<priority-label
|
||||
:priority="t.priority"
|
||||
:done="t.done"
|
||||
:show-all="true"
|
||||
/>
|
||||
</td>
|
||||
<td v-if="activeColumns.labels">
|
||||
<labels :labels="t.labels" />
|
||||
</td>
|
||||
<td v-if="activeColumns.assignees">
|
||||
<user
|
||||
v-for="(a, i) in t.assignees"
|
||||
:key="t.id + 'assignee' + a.id + i"
|
||||
:avatar-size="27"
|
||||
:is-inline="true"
|
||||
:show-username="false"
|
||||
:user="a"
|
||||
/>
|
||||
</td>
|
||||
<date-table-cell
|
||||
v-if="activeColumns.dueDate"
|
||||
:date="t.dueDate"
|
||||
/>
|
||||
<date-table-cell
|
||||
v-if="activeColumns.startDate"
|
||||
:date="t.startDate"
|
||||
/>
|
||||
<date-table-cell
|
||||
v-if="activeColumns.endDate"
|
||||
:date="t.endDate"
|
||||
/>
|
||||
<td v-if="activeColumns.percentDone">
|
||||
{{ t.percentDone * 100 }}%
|
||||
</td>
|
||||
<date-table-cell
|
||||
v-if="activeColumns.created"
|
||||
:date="t.created"
|
||||
/>
|
||||
<date-table-cell
|
||||
v-if="activeColumns.updated"
|
||||
:date="t.updated"
|
||||
/>
|
||||
<td v-if="activeColumns.createdBy">
|
||||
<user
|
||||
:avatar-size="27"
|
||||
:show-username="false"
|
||||
:user="t.createdBy"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<Pagination
|
||||
:total-pages="totalPages"
|
||||
:current-page="currentPage"
|
||||
/>
|
||||
</card>
|
||||
</div>
|
||||
</template>
|
||||
</ListWrapper>
|
||||
<Pagination
|
||||
:total-pages="totalPages"
|
||||
:current-page="currentPage"
|
||||
/>
|
||||
</card>
|
||||
</div>
|
||||
</template>
|
||||
</ListWrapper>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,49 +1,57 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{ 'is-loading': listService.loading, 'is-archived': currentList.isArchived}"
|
||||
class="loader-container"
|
||||
>
|
||||
<div class="switch-view-container">
|
||||
<div class="switch-view">
|
||||
<router-link
|
||||
v-shortcut="'g l'"
|
||||
:title="$t('keyboardShortcuts.list.switchToListView')"
|
||||
:class="{'is-active': viewName === 'list'}"
|
||||
:to="{ name: 'list.list', params: { listId } }">
|
||||
{{ $t('list.list.title') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-shortcut="'g g'"
|
||||
:title="$t('keyboardShortcuts.list.switchToGanttView')"
|
||||
:class="{'is-active': viewName === 'gantt'}"
|
||||
:to="{ name: 'list.gantt', params: { listId } }">
|
||||
{{ $t('list.gantt.title') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-shortcut="'g t'"
|
||||
:title="$t('keyboardShortcuts.list.switchToTableView')"
|
||||
:class="{'is-active': viewName === 'table'}"
|
||||
:to="{ name: 'list.table', params: { listId } }">
|
||||
{{ $t('list.table.title') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-shortcut="'g k'"
|
||||
:title="$t('keyboardShortcuts.list.switchToKanbanView')"
|
||||
:class="{'is-active': viewName === 'kanban'}"
|
||||
:to="{ name: 'list.kanban', params: { listId } }">
|
||||
{{ $t('list.kanban.title') }}
|
||||
</router-link>
|
||||
</div>
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<Message variant="warning" v-if="currentList.isArchived" class="mb-4">
|
||||
{{ $t('list.archived') }}
|
||||
</Message>
|
||||
</transition>
|
||||
<div
|
||||
:class="{ 'is-loading': listService.loading, 'is-archived': currentList.isArchived}"
|
||||
class="loader-container"
|
||||
>
|
||||
<div class="switch-view-container">
|
||||
<div class="switch-view">
|
||||
<router-link
|
||||
v-shortcut="'g l'"
|
||||
:title="$t('keyboardShortcuts.list.switchToListView')"
|
||||
:class="{'is-active': viewName === 'list'}"
|
||||
:to="{ name: 'list.list', params: { listId } }"
|
||||
>
|
||||
{{ $t('list.list.title') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-shortcut="'g g'"
|
||||
:title="$t('keyboardShortcuts.list.switchToGanttView')"
|
||||
:class="{'is-active': viewName === 'gantt'}"
|
||||
:to="{ name: 'list.gantt', params: { listId } }"
|
||||
>
|
||||
{{ $t('list.gantt.title') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-shortcut="'g t'"
|
||||
:title="$t('keyboardShortcuts.list.switchToTableView')"
|
||||
:class="{'is-active': viewName === 'table'}"
|
||||
:to="{ name: 'list.table', params: { listId } }"
|
||||
>
|
||||
{{ $t('list.table.title') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-shortcut="'g k'"
|
||||
:title="$t('keyboardShortcuts.list.switchToKanbanView')"
|
||||
:class="{'is-active': viewName === 'kanban'}"
|
||||
:to="{ name: 'list.kanban', params: { listId } }"
|
||||
>
|
||||
{{ $t('list.kanban.title') }}
|
||||
</router-link>
|
||||
</div>
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<Message
|
||||
v-if="currentList.isArchived"
|
||||
variant="warning"
|
||||
class="mb-4"
|
||||
>
|
||||
{{ $t('list.archived') }}
|
||||
</Message>
|
||||
</transition>
|
||||
|
||||
<slot v-if="loadedListId"/>
|
||||
</div>
|
||||
<slot v-if="loadedListId" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,34 +1,44 @@
|
|||
<template>
|
||||
<create-edit :title="$t('list.create.header')" @create="createNewList()" :primary-disabled="list.title === ''">
|
||||
<div class="field">
|
||||
<label class="label" for="listTitle">{{ $t('list.title') }}</label>
|
||||
<div
|
||||
:class="{ 'is-loading': listService.loading }"
|
||||
class="control"
|
||||
>
|
||||
<input
|
||||
:class="{ disabled: listService.loading }"
|
||||
@keyup.enter="createNewList()"
|
||||
@keyup.esc="$router.back()"
|
||||
class="input"
|
||||
:placeholder="$t('list.create.titlePlaceholder')"
|
||||
type="text"
|
||||
name="listTitle"
|
||||
v-focus
|
||||
v-model="list.title"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="showError && list.title === ''">
|
||||
{{ $t('list.create.addTitleRequired') }}
|
||||
</p>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('list.color') }}</label>
|
||||
<div class="control">
|
||||
<color-picker v-model="list.hexColor" />
|
||||
</div>
|
||||
</div>
|
||||
</create-edit>
|
||||
<create-edit
|
||||
:title="$t('list.create.header')"
|
||||
:primary-disabled="list.title === ''"
|
||||
@create="createNewList()"
|
||||
>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="listTitle"
|
||||
>{{ $t('list.title') }}</label>
|
||||
<div
|
||||
:class="{ 'is-loading': listService.loading }"
|
||||
class="control"
|
||||
>
|
||||
<input
|
||||
v-model="list.title"
|
||||
v-focus
|
||||
:class="{ disabled: listService.loading }"
|
||||
class="input"
|
||||
:placeholder="$t('list.create.titlePlaceholder')"
|
||||
type="text"
|
||||
name="listTitle"
|
||||
@keyup.enter="createNewList()"
|
||||
@keyup.esc="$router.back()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
v-if="showError && list.title === ''"
|
||||
class="help is-danger"
|
||||
>
|
||||
{{ $t('list.create.addTitleRequired') }}
|
||||
</p>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('list.color') }}</label>
|
||||
<div class="control">
|
||||
<color-picker v-model="list.hexColor" />
|
||||
</div>
|
||||
</div>
|
||||
</create-edit>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
@submit="archiveList()"
|
||||
>
|
||||
<template #header><span>{{ list.isArchived ? $t('list.archive.unarchive') : $t('list.archive.archive') }}</span></template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
@submit="archiveList()"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{ list.isArchived ? $t('list.archive.unarchive') : $t('list.archive.archive') }}</span>
|
||||
</template>
|
||||
|
||||
<template #text>
|
||||
<p>{{ list.isArchived ? $t('list.archive.unarchiveText') : $t('list.archive.archiveText') }}</p>
|
||||
</template>
|
||||
</modal>
|
||||
<template #text>
|
||||
<p>{{ list.isArchived ? $t('list.archive.unarchiveText') : $t('list.archive.archiveText') }}</p>
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {name: 'list-setting-archive'}
|
||||
export default {name: 'ListSettingArchive'}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,100 +1,112 @@
|
|||
<template>
|
||||
<create-edit
|
||||
v-if="uploadBackgroundEnabled || unsplashBackgroundEnabled"
|
||||
:title="$t('list.background.title')"
|
||||
:loading="backgroundService.loading"
|
||||
class="list-background-setting"
|
||||
:wide="true"
|
||||
>
|
||||
<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="backgroundUploadInput?.click()"
|
||||
variant="primary"
|
||||
>
|
||||
{{ $t('list.background.upload') }}
|
||||
</x-button>
|
||||
</div>
|
||||
<template v-if="unsplashBackgroundEnabled">
|
||||
<input
|
||||
:class="{'is-loading': backgroundService.loading}"
|
||||
@keyup="debounceNewBackgroundSearch()"
|
||||
class="input is-expanded"
|
||||
:placeholder="$t('list.background.searchPlaceholder')"
|
||||
type="text"
|
||||
v-model="backgroundSearchTerm"
|
||||
/>
|
||||
<create-edit
|
||||
v-if="uploadBackgroundEnabled || unsplashBackgroundEnabled"
|
||||
:title="$t('list.background.title')"
|
||||
:loading="backgroundService.loading"
|
||||
class="list-background-setting"
|
||||
:wide="true"
|
||||
>
|
||||
<div
|
||||
v-if="uploadBackgroundEnabled"
|
||||
class="mb-4"
|
||||
>
|
||||
<input
|
||||
ref="backgroundUploadInput"
|
||||
accept="image/*"
|
||||
class="is-hidden"
|
||||
type="file"
|
||||
@change="uploadBackground"
|
||||
>
|
||||
<x-button
|
||||
:loading="backgroundUploadService.loading"
|
||||
variant="primary"
|
||||
@click="backgroundUploadInput?.click()"
|
||||
>
|
||||
{{ $t('list.background.upload') }}
|
||||
</x-button>
|
||||
</div>
|
||||
<template v-if="unsplashBackgroundEnabled">
|
||||
<input
|
||||
v-model="backgroundSearchTerm"
|
||||
:class="{'is-loading': backgroundService.loading}"
|
||||
class="input is-expanded"
|
||||
:placeholder="$t('list.background.searchPlaceholder')"
|
||||
type="text"
|
||||
@keyup="debounceNewBackgroundSearch()"
|
||||
>
|
||||
|
||||
<p class="unsplash-credit">
|
||||
<BaseButton class="unsplash-credit__link" href="https://unsplash.com">{{ $t('list.background.poweredByUnsplash') }}</BaseButton>
|
||||
</p>
|
||||
<p class="unsplash-credit">
|
||||
<BaseButton
|
||||
class="unsplash-credit__link"
|
||||
href="https://unsplash.com"
|
||||
>
|
||||
{{ $t('list.background.poweredByUnsplash') }}
|
||||
</BaseButton>
|
||||
</p>
|
||||
|
||||
<ul class="image-search__result-list">
|
||||
<li
|
||||
v-for="im in backgroundSearchResult"
|
||||
class="image-search__result-item"
|
||||
:key="im.id"
|
||||
:style="{'background-image': `url(${backgroundBlurHashes[im.id]})`}"
|
||||
>
|
||||
<transition name="fade">
|
||||
<BaseButton
|
||||
v-if="backgroundThumbs[im.id]"
|
||||
class="image-search__image-button"
|
||||
@click="setBackground(im.id)"
|
||||
>
|
||||
<img class="image-search__image" :src="backgroundThumbs[im.id]" alt="" />
|
||||
</BaseButton>
|
||||
</transition>
|
||||
<ul class="image-search__result-list">
|
||||
<li
|
||||
v-for="im in backgroundSearchResult"
|
||||
:key="im.id"
|
||||
class="image-search__result-item"
|
||||
:style="{'background-image': `url(${backgroundBlurHashes[im.id]})`}"
|
||||
>
|
||||
<transition name="fade">
|
||||
<BaseButton
|
||||
v-if="backgroundThumbs[im.id]"
|
||||
class="image-search__image-button"
|
||||
@click="setBackground(im.id)"
|
||||
>
|
||||
<img
|
||||
class="image-search__image"
|
||||
:src="backgroundThumbs[im.id]"
|
||||
alt=""
|
||||
>
|
||||
</BaseButton>
|
||||
</transition>
|
||||
|
||||
<BaseButton
|
||||
:href="`https://unsplash.com/@${im.info.author}`"
|
||||
class="image-search__info"
|
||||
>
|
||||
{{ im.info.authorName }}
|
||||
</BaseButton>
|
||||
</li>
|
||||
</ul>
|
||||
<x-button
|
||||
v-if="backgroundSearchResult.length > 0"
|
||||
:disabled="backgroundService.loading"
|
||||
@click="searchBackgrounds(currentPage + 1)"
|
||||
class="is-load-more-button mt-4"
|
||||
:shadow="false"
|
||||
variant="secondary"
|
||||
>
|
||||
{{ backgroundService.loading ? $t('misc.loading') : $t('list.background.loadMore') }}
|
||||
</x-button>
|
||||
</template>
|
||||
<BaseButton
|
||||
:href="`https://unsplash.com/@${im.info.author}`"
|
||||
class="image-search__info"
|
||||
>
|
||||
{{ im.info.authorName }}
|
||||
</BaseButton>
|
||||
</li>
|
||||
</ul>
|
||||
<x-button
|
||||
v-if="backgroundSearchResult.length > 0"
|
||||
:disabled="backgroundService.loading"
|
||||
class="is-load-more-button mt-4"
|
||||
:shadow="false"
|
||||
variant="secondary"
|
||||
@click="searchBackgrounds(currentPage + 1)"
|
||||
>
|
||||
{{ backgroundService.loading ? $t('misc.loading') : $t('list.background.loadMore') }}
|
||||
</x-button>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<x-button
|
||||
v-if="hasBackground"
|
||||
:shadow="false"
|
||||
variant="tertiary"
|
||||
class="is-danger"
|
||||
@click.prevent.stop="removeBackground"
|
||||
>
|
||||
{{ $t('list.background.remove') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
@click.prevent.stop="$router.back()"
|
||||
>
|
||||
{{ $t('misc.close') }}
|
||||
</x-button>
|
||||
</template>
|
||||
</create-edit>
|
||||
<template #footer>
|
||||
<x-button
|
||||
v-if="hasBackground"
|
||||
:shadow="false"
|
||||
variant="tertiary"
|
||||
class="is-danger"
|
||||
@click.prevent.stop="removeBackground"
|
||||
>
|
||||
{{ $t('list.background.remove') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
@click.prevent.stop="$router.back()"
|
||||
>
|
||||
{{ $t('misc.close') }}
|
||||
</x-button>
|
||||
</template>
|
||||
</create-edit>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default { name: 'list-setting-background' }
|
||||
export default { name: 'ListSettingBackground' }
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,29 +1,37 @@
|
|||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
@submit="deleteList()"
|
||||
>
|
||||
<template #header><span>{{ $t('list.delete.header') }}</span></template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
@submit="deleteList()"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{ $t('list.delete.header') }}</span>
|
||||
</template>
|
||||
|
||||
<template #text>
|
||||
<p>
|
||||
{{ $t('list.delete.text1') }}
|
||||
</p>
|
||||
<template #text>
|
||||
<p>
|
||||
{{ $t('list.delete.text1') }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong v-if="totalTasks !== null" class="has-text-white">
|
||||
{{
|
||||
totalTasks > 0 ? $t('list.delete.tasksToDelete', {count: totalTasks}) : $t('list.delete.noTasksToDelete')
|
||||
}}
|
||||
</strong>
|
||||
<Loading v-else class="is-loading-small"/>
|
||||
</p>
|
||||
<p>
|
||||
<strong
|
||||
v-if="totalTasks !== null"
|
||||
class="has-text-white"
|
||||
>
|
||||
{{
|
||||
totalTasks > 0 ? $t('list.delete.tasksToDelete', {count: totalTasks}) : $t('list.delete.noTasksToDelete')
|
||||
}}
|
||||
</strong>
|
||||
<Loading
|
||||
v-else
|
||||
class="is-loading-small"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{ $t('misc.cannotBeUndone') }}
|
||||
</p>
|
||||
</template>
|
||||
</modal>
|
||||
<p>
|
||||
{{ $t('misc.cannotBeUndone') }}
|
||||
</p>
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
<template>
|
||||
<create-edit
|
||||
:title="$t('list.duplicate.title')"
|
||||
primary-icon="paste"
|
||||
:primary-label="$t('list.duplicate.label')"
|
||||
@primary="duplicateList"
|
||||
:loading="listDuplicateService.loading"
|
||||
>
|
||||
<p>{{ $t('list.duplicate.text') }}</p>
|
||||
<create-edit
|
||||
:title="$t('list.duplicate.title')"
|
||||
primary-icon="paste"
|
||||
:primary-label="$t('list.duplicate.label')"
|
||||
:loading="listDuplicateService.loading"
|
||||
@primary="duplicateList"
|
||||
>
|
||||
<p>{{ $t('list.duplicate.text') }}</p>
|
||||
|
||||
<Multiselect
|
||||
:placeholder="$t('namespace.search')"
|
||||
@search="findNamespaces"
|
||||
:search-results="namespaces"
|
||||
@select="selectNamespace"
|
||||
label="title"
|
||||
:search-delay="10"
|
||||
/>
|
||||
</create-edit>
|
||||
<Multiselect
|
||||
:placeholder="$t('namespace.search')"
|
||||
:search-results="namespaces"
|
||||
label="title"
|
||||
:search-delay="10"
|
||||
@search="findNamespaces"
|
||||
@select="selectNamespace"
|
||||
/>
|
||||
</create-edit>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,72 +1,80 @@
|
|||
<template>
|
||||
<create-edit
|
||||
:title="$t('list.edit.header')"
|
||||
primary-icon=""
|
||||
:primary-label="$t('misc.save')"
|
||||
@primary="save"
|
||||
:tertiary="$t('misc.delete')"
|
||||
@tertiary="$router.push({ name: 'list.settings.delete', params: { id: listId } })"
|
||||
>
|
||||
<div class="field">
|
||||
<label class="label" for="title">{{ $t('list.title') }}</label>
|
||||
<div class="control">
|
||||
<input
|
||||
:class="{ 'disabled': isLoading}"
|
||||
:disabled="isLoading || undefined"
|
||||
@keyup.enter="save"
|
||||
class="input"
|
||||
id="title"
|
||||
:placeholder="$t('list.edit.titlePlaceholder')"
|
||||
type="text"
|
||||
v-focus
|
||||
v-model="list.title"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="identifier"
|
||||
v-tooltip="$t('list.edit.identifierTooltip')">
|
||||
{{ $t('list.edit.identifier') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<input
|
||||
:class="{ 'disabled': isLoading}"
|
||||
:disabled="isLoading || undefined"
|
||||
@keyup.enter="save"
|
||||
class="input"
|
||||
id="identifier"
|
||||
:placeholder="$t('list.edit.identifierPlaceholder')"
|
||||
type="text"
|
||||
v-focus
|
||||
v-model="list.identifier"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="listdescription">{{ $t('list.edit.description') }}</label>
|
||||
<div class="control">
|
||||
<Editor
|
||||
:class="{ 'disabled': isLoading}"
|
||||
:disabled="isLoading"
|
||||
:previewIsDefault="false"
|
||||
id="listdescription"
|
||||
:placeholder="$t('list.edit.descriptionPlaceholder')"
|
||||
v-model="list.description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('list.edit.color') }}</label>
|
||||
<div class="control">
|
||||
<color-picker v-model="list.hexColor"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</create-edit>
|
||||
<create-edit
|
||||
:title="$t('list.edit.header')"
|
||||
primary-icon=""
|
||||
:primary-label="$t('misc.save')"
|
||||
:tertiary="$t('misc.delete')"
|
||||
@primary="save"
|
||||
@tertiary="$router.push({ name: 'list.settings.delete', params: { id: listId } })"
|
||||
>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="title"
|
||||
>{{ $t('list.title') }}</label>
|
||||
<div class="control">
|
||||
<input
|
||||
id="title"
|
||||
v-model="list.title"
|
||||
v-focus
|
||||
:class="{ 'disabled': isLoading}"
|
||||
:disabled="isLoading || undefined"
|
||||
class="input"
|
||||
:placeholder="$t('list.edit.titlePlaceholder')"
|
||||
type="text"
|
||||
@keyup.enter="save"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label
|
||||
v-tooltip="$t('list.edit.identifierTooltip')"
|
||||
class="label"
|
||||
for="identifier"
|
||||
>
|
||||
{{ $t('list.edit.identifier') }}
|
||||
</label>
|
||||
<div class="control">
|
||||
<input
|
||||
id="identifier"
|
||||
v-model="list.identifier"
|
||||
v-focus
|
||||
:class="{ 'disabled': isLoading}"
|
||||
:disabled="isLoading || undefined"
|
||||
class="input"
|
||||
:placeholder="$t('list.edit.identifierPlaceholder')"
|
||||
type="text"
|
||||
@keyup.enter="save"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
for="listdescription"
|
||||
>{{ $t('list.edit.description') }}</label>
|
||||
<div class="control">
|
||||
<Editor
|
||||
id="listdescription"
|
||||
v-model="list.description"
|
||||
:class="{ 'disabled': isLoading}"
|
||||
:disabled="isLoading"
|
||||
:preview-is-default="false"
|
||||
:placeholder="$t('list.edit.descriptionPlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ $t('list.edit.color') }}</label>
|
||||
<div class="control">
|
||||
<color-picker v-model="list.hexColor" />
|
||||
</div>
|
||||
</div>
|
||||
</create-edit>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default { name: 'list-setting-edit' }
|
||||
export default { name: 'ListSettingEdit' }
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -1,29 +1,33 @@
|
|||
<template>
|
||||
<create-edit
|
||||
:title="$t('list.share.header')"
|
||||
primary-label=""
|
||||
>
|
||||
<template v-if="list">
|
||||
<userTeam
|
||||
:id="list.id"
|
||||
:userIsAdmin="userIsAdmin"
|
||||
shareType="user"
|
||||
type="list"
|
||||
/>
|
||||
<userTeam
|
||||
:id="list.id"
|
||||
:userIsAdmin="userIsAdmin"
|
||||
shareType="team"
|
||||
type="list"
|
||||
/>
|
||||
</template>
|
||||
<create-edit
|
||||
:title="$t('list.share.header')"
|
||||
primary-label=""
|
||||
>
|
||||
<template v-if="list">
|
||||
<userTeam
|
||||
:id="list.id"
|
||||
:user-is-admin="userIsAdmin"
|
||||
share-type="user"
|
||||
type="list"
|
||||
/>
|
||||
<userTeam
|
||||
:id="list.id"
|
||||
:user-is-admin="userIsAdmin"
|
||||
share-type="team"
|
||||
type="list"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<link-sharing :list-id="listId" v-if="linkSharingEnabled" class="mt-4"/>
|
||||
</create-edit>
|
||||
<link-sharing
|
||||
v-if="linkSharingEnabled"
|
||||
:list-id="listId"
|
||||
class="mt-4"
|
||||
/>
|
||||
</create-edit>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {name: 'list-setting-share'}
|
||||
export default {name: 'ListSettingShare'}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
<template>
|
||||
<div class="content">
|
||||
<h1>{{ $t('migrate.title') }}</h1>
|
||||
<p>{{ $t('migrate.description') }}</p>
|
||||
<div class="migration-services">
|
||||
<router-link
|
||||
v-for="{name, id, icon} in availableMigrators"
|
||||
:key="id"
|
||||
class="migration-service-link"
|
||||
:to="{name: 'migrate.service', params: {service: id}}"
|
||||
>
|
||||
<img
|
||||
class="migration-service-image"
|
||||
:alt="name"
|
||||
:src="icon"
|
||||
/>
|
||||
{{ name }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h1>{{ $t('migrate.title') }}</h1>
|
||||
<p>{{ $t('migrate.description') }}</p>
|
||||
<div class="migration-services">
|
||||
<router-link
|
||||
v-for="{name, id, icon} in availableMigrators"
|
||||
:key="id"
|
||||
class="migration-service-link"
|
||||
:to="{name: 'migrate.service', params: {service: id}}"
|
||||
>
|
||||
<img
|
||||
class="migration-service-image"
|
||||
:alt="name"
|
||||
:src="icon"
|
||||
>
|
||||
{{ name }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue