forked from vikunja/frontend
Compare commits
59 Commits
8db3c6c09f
...
7b17ccbf1f
Author | SHA1 | Date | |
---|---|---|---|
7b17ccbf1f | |||
ce7563ea4c | |||
d9f3555d8d | |||
b2dd63630c | |||
db1a41f845 | |||
08ae0046de | |||
c173542b23 | |||
518417c0de | |||
c2e58a2320 | |||
d94a25c83f | |||
ad9ca61969 | |||
d28d9218bd | |||
af08713bf0 | |||
0b01f2aace | |||
579de70a7a | |||
660ab928a2 | |||
9394f57fc9 | |||
873169c371 | |||
098fd0a875 | |||
c0cd69dd82 | |||
4666087aa9 | |||
56147dc9fb | |||
ff48178051 | |||
cb3f269937 | |||
4c560f1a03 | |||
0fe2a16a7c | |||
9cebf5305a | |||
71c8540c74 | |||
8183fce829 | |||
3becf8738b | |||
9ddb55a5ef | |||
cdb63b578d | |||
d6a10b01dd | |||
b74c961723 | |||
8b0e88b574 | |||
175fb02629 | |||
dac9d918b5 | |||
e7de930129 | |||
a0d0c2cb1f | |||
a4d3cafdf1 | |||
f5bb697032 | |||
62bbffb17e | |||
c2d5370e4a | |||
6dc02c45dd | |||
|
0456f4a041 | ||
c1dd20a30b | |||
acc7bf4305 | |||
86688d7e95 | |||
aaee49b70e | |||
6b23b954a8 | |||
9fd2f4ea5c | |||
dca05f852c | |||
5aa6cce185 | |||
d96ea384dc | |||
5d33144b8e | |||
d462d56202 | |||
2648592ac0 | |||
1ac78729a4 | |||
9d1195a2ed |
44
.gitea/issue_template.md
Normal file
44
.gitea/issue_template.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Please fill out this issue template to report a bug.
|
||||||
|
If you want to propose a new feature, please open a discussion thread in the forum: https://community.vikunja.io
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Version information:**
|
||||||
|
|
||||||
|
Frontend Version:
|
||||||
|
API Version:
|
||||||
|
Browser and OS Version:
|
||||||
|
|
||||||
|
**Steps to reproduce:**
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Add clear steps to reproduce the bug. Provide screenshots where applicable.
|
||||||
|
-->
|
||||||
|
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
...
|
||||||
|
|
||||||
|
**Expected behavior:**
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Describe what happened.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Actual behavior:**
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Describe what happened instead.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Checklist:**
|
||||||
|
|
||||||
|
* [ ] I have provided all required information
|
||||||
|
* [ ] I am using the latest release or the latest unstable build
|
||||||
|
* [ ] I was able to reproduce the bug on [try](https://try.vikunja.io)
|
66
package.json
66
package.json
|
@ -18,19 +18,19 @@
|
||||||
"browserslist:update": "npx browserslist@latest --update-db"
|
"browserslist:update": "npx browserslist@latest --update-db"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@github/hotkey": "2.0.0",
|
"@github/hotkey": "2.0.1",
|
||||||
"@kyvg/vue3-notification": "2.3.5",
|
"@kyvg/vue3-notification": "2.3.6",
|
||||||
"@sentry/tracing": "7.7.0",
|
"@sentry/tracing": "7.8.0",
|
||||||
"@sentry/vue": "7.7.0",
|
"@sentry/vue": "7.8.0",
|
||||||
"@types/is-touch-device": "1.0.0",
|
"@types/is-touch-device": "1.0.0",
|
||||||
"@types/sortablejs": "1.13.0",
|
"@types/sortablejs": "1.13.0",
|
||||||
"@vueuse/core": "8.9.3",
|
"@vueuse/core": "9.0.2",
|
||||||
"@vueuse/router": "8.9.3",
|
"@vueuse/router": "9.0.2",
|
||||||
"blurhash": "1.1.5",
|
"blurhash": "1.1.5",
|
||||||
"bulma-css-variables": "0.9.33",
|
"bulma-css-variables": "0.9.33",
|
||||||
"camel-case": "4.1.2",
|
"camel-case": "4.1.2",
|
||||||
"date-fns": "2.28.0",
|
"date-fns": "2.29.1",
|
||||||
"dompurify": "2.3.9",
|
"dompurify": "2.3.10",
|
||||||
"easymde": "2.16.1",
|
"easymde": "2.16.1",
|
||||||
"flatpickr": "4.6.13",
|
"flatpickr": "4.6.13",
|
||||||
"flexsearch": "0.7.21",
|
"flexsearch": "0.7.21",
|
||||||
|
@ -48,51 +48,51 @@
|
||||||
"vue-advanced-cropper": "2.8.3",
|
"vue-advanced-cropper": "2.8.3",
|
||||||
"vue-drag-resize": "2.0.3",
|
"vue-drag-resize": "2.0.3",
|
||||||
"vue-flatpickr-component": "9.0.6",
|
"vue-flatpickr-component": "9.0.6",
|
||||||
"vue-i18n": "9.2.0-beta.38",
|
"vue-i18n": "9.2.0-beta.40",
|
||||||
"vue-router": "4.1.2",
|
"vue-router": "4.1.3",
|
||||||
"vuex": "4.0.2",
|
"vuex": "4.0.2",
|
||||||
"workbox-precaching": "6.5.3",
|
"workbox-precaching": "6.5.3",
|
||||||
"zhyswan-vuedraggable": "4.1.3"
|
"zhyswan-vuedraggable": "4.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@4tw/cypress-drag-drop": "2.2.1",
|
"@4tw/cypress-drag-drop": "2.2.1",
|
||||||
"@cypress/vite-dev-server": "2.2.3",
|
"@cypress/vite-dev-server": "3.0.0",
|
||||||
"@cypress/vue": "3.1.2",
|
"@cypress/vue": "4.0.0",
|
||||||
"@faker-js/faker": "7.3.0",
|
"@faker-js/faker": "7.3.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.1.1",
|
"@fortawesome/fontawesome-svg-core": "6.1.2",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.1.1",
|
"@fortawesome/free-regular-svg-icons": "6.1.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.1.1",
|
"@fortawesome/free-solid-svg-icons": "6.1.2",
|
||||||
"@fortawesome/vue-fontawesome": "3.0.1",
|
"@fortawesome/vue-fontawesome": "3.0.1",
|
||||||
"@types/flexsearch": "0.7.3",
|
"@types/flexsearch": "0.7.3",
|
||||||
"@typescript-eslint/eslint-plugin": "5.30.6",
|
"@typescript-eslint/eslint-plugin": "5.31.0",
|
||||||
"@typescript-eslint/parser": "5.30.6",
|
"@typescript-eslint/parser": "5.31.0",
|
||||||
"@vitejs/plugin-legacy": "1.8.2",
|
"@vitejs/plugin-legacy": "2.0.0",
|
||||||
"@vitejs/plugin-vue": "2.3.3",
|
"@vitejs/plugin-vue": "3.0.1",
|
||||||
"@vue/eslint-config-typescript": "11.0.0",
|
"@vue/eslint-config-typescript": "11.0.0",
|
||||||
"@vue/test-utils": "2.0.2",
|
"@vue/test-utils": "2.0.2",
|
||||||
"@vue/tsconfig": "0.1.3",
|
"@vue/tsconfig": "0.1.3",
|
||||||
"autoprefixer": "10.4.7",
|
"autoprefixer": "10.4.8",
|
||||||
"axios": "0.27.2",
|
"axios": "0.27.2",
|
||||||
"browserslist": "4.21.1",
|
"browserslist": "4.21.3",
|
||||||
"caniuse-lite": "1.0.30001363",
|
"caniuse-lite": "1.0.30001373",
|
||||||
"cypress": "10.3.0",
|
"cypress": "10.3.1",
|
||||||
"esbuild": "0.14.49",
|
"esbuild": "0.14.51",
|
||||||
"eslint": "8.19.0",
|
"eslint": "8.20.0",
|
||||||
"eslint-plugin-vue": "9.2.0",
|
"eslint-plugin-vue": "9.3.0",
|
||||||
"express": "4.18.1",
|
"express": "4.18.1",
|
||||||
"happy-dom": "6.0.3",
|
"happy-dom": "6.0.4",
|
||||||
"netlify-cli": "10.9.0",
|
"netlify-cli": "10.13.0",
|
||||||
"postcss": "8.4.14",
|
"postcss": "8.4.14",
|
||||||
"postcss-preset-env": "7.7.2",
|
"postcss-preset-env": "7.7.2",
|
||||||
"rollup": "2.77.0",
|
"rollup": "2.77.2",
|
||||||
"rollup-plugin-visualizer": "5.7.1",
|
"rollup-plugin-visualizer": "5.7.1",
|
||||||
"sass": "1.53.0",
|
"sass": "1.54.0",
|
||||||
"typescript": "4.7.4",
|
"typescript": "4.7.4",
|
||||||
"vite": "2.9.14",
|
"vite": "3.0.4",
|
||||||
"vite-plugin-pwa": "0.12.3",
|
"vite-plugin-pwa": "0.12.3",
|
||||||
"vite-svg-loader": "3.4.0",
|
"vite-svg-loader": "3.4.0",
|
||||||
"vitest": "0.18.1",
|
"vitest": "0.20.2",
|
||||||
"vue-tsc": "0.38.7",
|
"vue-tsc": "0.39.4",
|
||||||
"wait-on": "6.0.1",
|
"wait-on": "6.0.1",
|
||||||
"workbox-cli": "6.5.3"
|
"workbox-cli": "6.5.3"
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,9 +10,7 @@
|
||||||
</no-auth-wrapper>
|
</no-auth-wrapper>
|
||||||
<Notification/>
|
<Notification/>
|
||||||
|
|
||||||
<transition name="fade">
|
<keyboard-shortcuts v-if="keyboardShortcutsActive"/>
|
||||||
<keyboard-shortcuts v-if="keyboardShortcutsActive"/>
|
|
||||||
</transition>
|
|
||||||
</ready>
|
</ready>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -48,44 +48,38 @@
|
||||||
</x-button>
|
</x-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<BaseButton
|
<dropdown-item
|
||||||
:to="{name: 'user.settings'}"
|
:to="{name: 'user.settings'}"
|
||||||
class="dropdown-item"
|
|
||||||
>
|
>
|
||||||
{{ $t('user.settings.title') }}
|
{{ $t('user.settings.title') }}
|
||||||
</BaseButton>
|
</dropdown-item>
|
||||||
<BaseButton
|
<dropdown-item
|
||||||
v-if="imprintUrl"
|
v-if="imprintUrl"
|
||||||
:href="imprintUrl"
|
:href="imprintUrl"
|
||||||
class="dropdown-item"
|
|
||||||
>
|
>
|
||||||
{{ $t('navigation.imprint') }}
|
{{ $t('navigation.imprint') }}
|
||||||
</BaseButton>
|
</dropdown-item>
|
||||||
<BaseButton
|
<dropdown-item
|
||||||
v-if="privacyPolicyUrl"
|
v-if="privacyPolicyUrl"
|
||||||
:href="privacyPolicyUrl"
|
:href="privacyPolicyUrl"
|
||||||
class="dropdown-item"
|
|
||||||
>
|
>
|
||||||
{{ $t('navigation.privacy') }}
|
{{ $t('navigation.privacy') }}
|
||||||
</BaseButton>
|
</dropdown-item>
|
||||||
<BaseButton
|
<dropdown-item
|
||||||
@click="$store.commit('keyboardShortcutsActive', true)"
|
@click="$store.commit('keyboardShortcutsActive', true)"
|
||||||
class="dropdown-item"
|
|
||||||
>
|
>
|
||||||
{{ $t('keyboardShortcuts.title') }}
|
{{ $t('keyboardShortcuts.title') }}
|
||||||
</BaseButton>
|
</dropdown-item>
|
||||||
<BaseButton
|
<dropdown-item
|
||||||
:to="{name: 'about'}"
|
:to="{name: 'about'}"
|
||||||
class="dropdown-item"
|
|
||||||
>
|
>
|
||||||
{{ $t('about.title') }}
|
{{ $t('about.title') }}
|
||||||
</BaseButton>
|
</dropdown-item>
|
||||||
<BaseButton
|
<dropdown-item
|
||||||
@click="logout()"
|
@click="logout()"
|
||||||
class="dropdown-item"
|
|
||||||
>
|
>
|
||||||
{{ $t('user.auth.logout') }}
|
{{ $t('user.auth.logout') }}
|
||||||
</BaseButton>
|
</dropdown-item>
|
||||||
</dropdown>
|
</dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -103,6 +97,7 @@ import Rights from '@/models/constants/rights.json'
|
||||||
import Update from '@/components/home/update.vue'
|
import Update from '@/components/home/update.vue'
|
||||||
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
|
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
|
||||||
import Dropdown from '@/components/misc/dropdown.vue'
|
import Dropdown from '@/components/misc/dropdown.vue'
|
||||||
|
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||||
import Notifications from '@/components/notifications/notifications.vue'
|
import Notifications from '@/components/notifications/notifications.vue'
|
||||||
import Logo from '@/components/home/Logo.vue'
|
import Logo from '@/components/home/Logo.vue'
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
|
|
@ -38,16 +38,14 @@
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</router-view>
|
</router-view>
|
||||||
|
|
||||||
<transition name="modal">
|
<modal
|
||||||
<modal
|
v-if="currentModal"
|
||||||
v-if="currentModal"
|
@close="closeModal()"
|
||||||
@close="closeModal()"
|
variant="scrolling"
|
||||||
variant="scrolling"
|
class="task-detail-view-modal"
|
||||||
class="task-detail-view-modal"
|
>
|
||||||
>
|
<component :is="currentModal"/>
|
||||||
<component :is="currentModal"/>
|
</modal>
|
||||||
</modal>
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
class="keyboard-shortcuts-button d-print-none"
|
class="keyboard-shortcuts-button d-print-none"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
>
|
>
|
||||||
<div class="container has-text-centered link-share-view">
|
<div class="container has-text-centered link-share-view">
|
||||||
<div class="column is-10 is-offset-1">
|
<div class="column is-10 is-offset-1">
|
||||||
<Logo class="logo" />
|
<Logo class="logo"/>
|
||||||
<h1
|
<h1
|
||||||
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
|
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
|
||||||
class="title">
|
class="title">
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
</h1>
|
</h1>
|
||||||
<div class="box has-text-left view">
|
<div class="box has-text-left view">
|
||||||
<router-view/>
|
<router-view/>
|
||||||
<PoweredByLink />
|
<PoweredByLink/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,14 +35,15 @@ const background = computed(() => store.state.background)
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.link-share-container.has-background .view {
|
.link-share-container.has-background .view {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
margin: 2rem 0 1.5rem;
|
margin: 2rem 0 1.5rem;
|
||||||
|
height: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
|
@ -55,6 +56,6 @@ const background = computed(() => store.state.background)
|
||||||
|
|
||||||
// FIXME: this should be defined somewhere deep
|
// FIXME: this should be defined somewhere deep
|
||||||
.link-share-view .card {
|
.link-share-view .card {
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -88,13 +88,12 @@
|
||||||
@end="saveListPosition"
|
@end="saveListPosition"
|
||||||
handle=".handle"
|
handle=".handle"
|
||||||
:disabled="n.id < 0 || undefined"
|
:disabled="n.id < 0 || undefined"
|
||||||
tag="transition-group"
|
tag="ul"
|
||||||
item-key="id"
|
item-key="id"
|
||||||
:data-namespace-id="n.id"
|
:data-namespace-id="n.id"
|
||||||
:data-namespace-index="nk"
|
:data-namespace-index="nk"
|
||||||
:component-data="{
|
:component-data="{
|
||||||
type: 'transition',
|
type: 'transition-group',
|
||||||
tag: 'ul',
|
|
||||||
name: !drag ? 'flip-list' : null,
|
name: !drag ? 'flip-list' : null,
|
||||||
class: [
|
class: [
|
||||||
'menu-list can-be-hidden',
|
'menu-list can-be-hidden',
|
||||||
|
@ -555,8 +554,4 @@ $vikunja-nav-selected-width: 0.4rem;
|
||||||
.namespaces-list.loader-container.is-loading {
|
.namespaces-list.loader-container.is-loading {
|
||||||
min-height: calc(100vh - #{$navbar-height + 1.5rem + 1rem + 1.5rem});
|
min-height: calc(100vh - #{$navbar-height + 1.5rem + 1rem + 1.5rem});
|
||||||
}
|
}
|
||||||
|
|
||||||
a.dropdown-item:hover {
|
|
||||||
background: var(--dropdown-item-hover-background-color) !important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<x-button
|
<x-button
|
||||||
class="datepicker__close-button is-fullwidth"
|
class="datepicker__close-button"
|
||||||
:shadow="false"
|
:shadow="false"
|
||||||
@click="close"
|
@click="close"
|
||||||
v-cy="'closeDatepicker'"
|
v-cy="'closeDatepicker'"
|
||||||
|
|
|
@ -56,12 +56,13 @@
|
||||||
{{ $t('menu.archive') }}
|
{{ $t('menu.archive') }}
|
||||||
</dropdown-item>
|
</dropdown-item>
|
||||||
<task-subscription
|
<task-subscription
|
||||||
class="dropdown-item has-no-shadow"
|
class="has-no-shadow"
|
||||||
:is-button="false"
|
:is-button="false"
|
||||||
entity="list"
|
entity="list"
|
||||||
:entity-id="list.id"
|
:entity-id="list.id"
|
||||||
:subscription="list.subscription"
|
:subscription="list.subscription"
|
||||||
@change="sub => subscription = sub"
|
@change="sub => subscription = sub"
|
||||||
|
type="dropdown"
|
||||||
/>
|
/>
|
||||||
<dropdown-item
|
<dropdown-item
|
||||||
:to="{ name: 'list.settings.delete', params: { listId: list.id } }"
|
:to="{ name: 'list.settings.delete', params: { listId: list.id } }"
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
v-model="value"
|
v-model="value"
|
||||||
ref="filters"
|
ref="filters"
|
||||||
class="filter-popup"
|
class="filter-popup"
|
||||||
:class="{'is-open': isOpen}"
|
|
||||||
/>
|
/>
|
||||||
</modal>
|
</modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,25 +1,95 @@
|
||||||
<template>
|
<template>
|
||||||
<router-link
|
<component
|
||||||
|
:is="componentNodeName"
|
||||||
|
v-bind="elementBindings"
|
||||||
:to="to"
|
:to="to"
|
||||||
class="dropdown-item">
|
class="dropdown-item">
|
||||||
<span class="icon" v-if="icon !== ''">
|
<span class="icon" v-if="icon">
|
||||||
<icon :icon="icon"/>
|
<icon :icon="icon"/>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</span>
|
</span>
|
||||||
</router-link>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineProps({
|
import {ref, useAttrs, watchEffect} from 'vue'
|
||||||
to: {
|
|
||||||
required: true,
|
const props = defineProps<{
|
||||||
},
|
to?: object,
|
||||||
icon: {
|
icon?: string | string[],
|
||||||
type: String,
|
}>()
|
||||||
required: false,
|
|
||||||
default: '',
|
const componentNodeName = ref<Node['nodeName']>('a')
|
||||||
},
|
const elementBindings = ref({})
|
||||||
|
|
||||||
|
const attrs = useAttrs()
|
||||||
|
watchEffect(() => {
|
||||||
|
let nodeName = 'a'
|
||||||
|
|
||||||
|
if (props.to) {
|
||||||
|
nodeName = 'router-link'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('href' in attrs) {
|
||||||
|
nodeName = 'BaseButton'
|
||||||
|
}
|
||||||
|
|
||||||
|
componentNodeName.value = nodeName
|
||||||
|
elementBindings.value = {
|
||||||
|
...attrs,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.dropdown-item {
|
||||||
|
color: var(--text);
|
||||||
|
display: block;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: $item-padding;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.dropdown-item,
|
||||||
|
button.dropdown-item {
|
||||||
|
text-align: inherit;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: left !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--grey-100) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
background-color: var(--link);
|
||||||
|
color: var(--link-invert);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
padding-right: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon:not(.has-text-success) {
|
||||||
|
color: var(--grey-300) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-text-danger .icon {
|
||||||
|
color: var(--danger) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="dropdown is-right is-active" ref="dropdown">
|
<div class="dropdown" ref="dropdown">
|
||||||
<slot name="trigger" :close="close" :toggleOpen="toggleOpen">
|
<slot name="trigger" :close="close" :toggleOpen="toggleOpen">
|
||||||
<BaseButton class="dropdown-trigger is-flex" @click="toggleOpen">
|
<BaseButton class="dropdown-trigger is-flex" @click="toggleOpen">
|
||||||
<icon :icon="triggerIcon" class="icon"/>
|
<icon :icon="triggerIcon" class="icon"/>
|
||||||
|
@ -51,7 +51,36 @@ onClickOutside(dropdown, (e: Event) => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.dropdown-menu .dropdown-content {
|
.dropdown {
|
||||||
|
display: inline-flex;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
min-width: 12rem;
|
||||||
|
padding-top: 4px;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
z-index: 20;
|
||||||
|
display: block;
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content {
|
||||||
|
background-color: var(--scheme-main);
|
||||||
|
border-radius: $radius;
|
||||||
|
padding-bottom: .5rem;
|
||||||
|
padding-top: .5rem;
|
||||||
box-shadow: var(--shadow-md);
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-divider {
|
||||||
|
background-color: var(--border-light);
|
||||||
|
border: none;
|
||||||
|
display: block;
|
||||||
|
height: 1px;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<!-- FIXME: transition should not be included in the modal -->
|
<!-- FIXME: transition should not be included in the modal -->
|
||||||
<transition name="modal">
|
<transition :name="transitionName">
|
||||||
<section
|
<section
|
||||||
v-if="enabled"
|
v-if="enabled"
|
||||||
class="modal-mask"
|
class="modal-mask"
|
||||||
|
|
|
@ -69,9 +69,9 @@ function createPagination(totalPages: number, currentPage: number) {
|
||||||
return pages
|
return pages
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRouteForPagination(page = 1, type = 'list') {
|
function getRouteForPagination(page = 1, type = null) {
|
||||||
return {
|
return {
|
||||||
name: 'list.' + type,
|
name: type,
|
||||||
params: {
|
params: {
|
||||||
type: type,
|
type: type,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<x-button
|
<x-button
|
||||||
v-if="isButton"
|
v-if="type === 'button'"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
:icon="iconName"
|
:icon="iconName"
|
||||||
v-tooltip="tooltipText"
|
v-tooltip="tooltipText"
|
||||||
|
@ -9,6 +9,15 @@
|
||||||
>
|
>
|
||||||
{{ buttonText }}
|
{{ buttonText }}
|
||||||
</x-button>
|
</x-button>
|
||||||
|
<DropdownItem
|
||||||
|
v-else-if="type === 'dropdown'"
|
||||||
|
v-tooltip="tooltipText"
|
||||||
|
@click="changeSubscription"
|
||||||
|
:class="{'is-disabled': disabled}"
|
||||||
|
:icon="iconName"
|
||||||
|
>
|
||||||
|
{{ buttonText }}
|
||||||
|
</DropdownItem>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-else
|
v-else
|
||||||
v-tooltip="tooltipText"
|
v-tooltip="tooltipText"
|
||||||
|
@ -27,6 +36,7 @@ import {computed, shallowRef} from 'vue'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||||
|
|
||||||
import SubscriptionService from '@/services/subscription'
|
import SubscriptionService from '@/services/subscription'
|
||||||
import SubscriptionModel from '@/models/subscription'
|
import SubscriptionModel from '@/models/subscription'
|
||||||
|
@ -34,15 +44,15 @@ import SubscriptionModel from '@/models/subscription'
|
||||||
import {success} from '@/message'
|
import {success} from '@/message'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
entity: string
|
entity: string
|
||||||
entityId: number
|
entityId: number
|
||||||
subscription: SubscriptionModel | null
|
subscription: SubscriptionModel | null
|
||||||
isButton?: boolean
|
type?: 'button' | 'dropdown' | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
isButton: true,
|
|
||||||
subscription: null,
|
subscription: null,
|
||||||
|
type: 'button',
|
||||||
})
|
})
|
||||||
|
|
||||||
const subscriptionEntity = computed<string | null>(() => props.subscription?.entity ?? null)
|
const subscriptionEntity = computed<string | null>(() => props.subscription?.entity ?? null)
|
||||||
|
|
|
@ -34,12 +34,13 @@
|
||||||
{{ $t('menu.archive') }}
|
{{ $t('menu.archive') }}
|
||||||
</dropdown-item>
|
</dropdown-item>
|
||||||
<task-subscription
|
<task-subscription
|
||||||
class="dropdown-item has-no-shadow"
|
class="has-no-shadow"
|
||||||
:is-button="false"
|
:is-button="false"
|
||||||
entity="namespace"
|
entity="namespace"
|
||||||
:entity-id="namespace.id"
|
:entity-id="namespace.id"
|
||||||
:subscription="subscription"
|
:subscription="subscription"
|
||||||
@change="sub => subscription = sub"
|
@change="sub => subscription = sub"
|
||||||
|
type="dropdown"
|
||||||
/>
|
/>
|
||||||
<dropdown-item
|
<dropdown-item
|
||||||
:to="{ name: 'namespace.settings.delete', params: { id: namespace.id } }"
|
:to="{ name: 'namespace.settings.delete', params: { id: namespace.id } }"
|
||||||
|
|
|
@ -526,7 +526,7 @@ export default defineComponent({
|
||||||
|
|
||||||
.result {
|
.result {
|
||||||
&-title {
|
&-title {
|
||||||
background: var(--grey-50);
|
background: var(--grey-100);
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
display: block;
|
display: block;
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
|
@ -549,7 +549,7 @@ export default defineComponent({
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:focus, &:hover {
|
&:focus, &:hover {
|
||||||
background: var(--grey-50);
|
background: var(--grey-100);
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,6 @@ const props = defineProps({
|
||||||
|
|
||||||
for (const e of prop) {
|
for (const e of prop) {
|
||||||
if (!isDate(e) && !isString(e)) {
|
if (!isDate(e) && !isString(e)) {
|
||||||
console.log('validation failed', e, e instanceof Date)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="control repeat-after-input">
|
<div class="control repeat-after-input">
|
||||||
<div class="buttons has-addons is-centered mt-2">
|
<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, 'days')">
|
||||||
<x-button variant="secondary" class="is-small" @click="() => setRepeatAfter(1, 'weeks')">{{ $t('task.repeat.everyWeek') }}</x-button>
|
{{ $t('task.repeat.everyDay') }}
|
||||||
<x-button variant="secondary" class="is-small" @click="() => setRepeatAfter(1, 'months')">{{ $t('task.repeat.everyMonth') }}</x-button>
|
</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>
|
||||||
<div class="is-flex is-align-items-center mb-2">
|
<div class="is-flex is-align-items-center mb-2">
|
||||||
<label for="repeatMode" class="is-fullwidth">
|
<label for="repeatMode" class="is-fullwidth">
|
||||||
|
@ -14,7 +20,10 @@
|
||||||
<select @change="updateData" v-model="task.repeatMode" id="repeatMode">
|
<select @change="updateData" v-model="task.repeatMode" id="repeatMode">
|
||||||
<option :value="repeatModes.REPEAT_MODE_DEFAULT">{{ $t('misc.default') }}</option>
|
<option :value="repeatModes.REPEAT_MODE_DEFAULT">{{ $t('misc.default') }}</option>
|
||||||
<option :value="repeatModes.REPEAT_MODE_MONTH">{{ $t('task.repeat.monthly') }}</option>
|
<option :value="repeatModes.REPEAT_MODE_MONTH">{{ $t('task.repeat.monthly') }}</option>
|
||||||
<option :value="repeatModes.REPEAT_MODE_FROM_CURRENT_DATE">{{ $t('task.repeat.fromCurrentDate') }}</option>
|
<option :value="repeatModes.REPEAT_MODE_FROM_CURRENT_DATE">{{
|
||||||
|
$t('task.repeat.fromCurrentDate')
|
||||||
|
}}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,6 +41,7 @@
|
||||||
:placeholder="$t('task.repeat.specifyAmount')"
|
:placeholder="$t('task.repeat.specifyAmount')"
|
||||||
v-model="repeatAfter.amount"
|
v-model="repeatAfter.amount"
|
||||||
type="number"
|
type="number"
|
||||||
|
min="0"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
@ -56,8 +66,10 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, reactive, watch} from 'vue'
|
import {ref, reactive, watch} from 'vue'
|
||||||
import repeatModes from '@/models/constants/taskRepeatModes'
|
import repeatModes from '@/models/constants/taskRepeatModes.json'
|
||||||
import TaskModel from '@/models/task'
|
import TaskModel from '@/models/task'
|
||||||
|
import {error} from '@/message'
|
||||||
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
@ -70,6 +82,8 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'change'])
|
const emit = defineEmits(['update:modelValue', 'change'])
|
||||||
|
|
||||||
const task = ref<TaskModel>()
|
const task = ref<TaskModel>()
|
||||||
|
@ -94,13 +108,18 @@ function updateData() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (repeatAfter.amount < 0) {
|
||||||
|
error({message: t('task.repeat.invalidAmount')})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(task.value.repeatAfter, repeatAfter)
|
Object.assign(task.value.repeatAfter, repeatAfter)
|
||||||
emit('update:modelValue', task.value)
|
emit('update:modelValue', task.value)
|
||||||
emit('change')
|
emit('change')
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRepeatAfter(amount: number, type) {
|
function setRepeatAfter(amount: number, type) {
|
||||||
Object.assign(repeatAfter, { amount, type})
|
Object.assign(repeatAfter, {amount, type})
|
||||||
updateData()
|
updateData()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
6
src/helpers/objectIsEmpty.ts
Normal file
6
src/helpers/objectIsEmpty.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// https://stackoverflow.com/a/32108184/10924593
|
||||||
|
export function objectIsEmpty(obj: any): boolean {
|
||||||
|
return obj
|
||||||
|
&& Object.keys(obj).length === 0
|
||||||
|
&& Object.getPrototypeOf(obj) === Object.prototype
|
||||||
|
}
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Days",
|
"days": "Days",
|
||||||
"weeks": "Weeks",
|
"weeks": "Weeks",
|
||||||
"months": "Months",
|
"months": "Months",
|
||||||
"years": "Years"
|
"years": "Years",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "You can use Quick Add Magic",
|
"hint": "You can use Quick Add Magic",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Dnů",
|
"days": "Dnů",
|
||||||
"weeks": "Týdny",
|
"weeks": "Týdny",
|
||||||
"months": "Měsíce",
|
"months": "Měsíce",
|
||||||
"years": "Roky"
|
"years": "Roky",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "Můžeš použít Kouzelné rychlé přidání",
|
"hint": "Můžeš použít Kouzelné rychlé přidání",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Tage",
|
"days": "Tage",
|
||||||
"weeks": "Wochen",
|
"weeks": "Wochen",
|
||||||
"months": "Monate",
|
"months": "Monate",
|
||||||
"years": "Jahre"
|
"years": "Jahre",
|
||||||
|
"invalidAmount": "Bitte mehr als 0 eingeben."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "Du kannst Quick Add Magic verwenden",
|
"hint": "Du kannst Quick Add Magic verwenden",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Tääg",
|
"days": "Tääg",
|
||||||
"weeks": "Wuchä",
|
"weeks": "Wuchä",
|
||||||
"months": "Monet",
|
"months": "Monet",
|
||||||
"years": "Jahr"
|
"years": "Jahr",
|
||||||
|
"invalidAmount": "Bitte mehr als 0 eingeben."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "Du chasch Quick Add Magic verwendä",
|
"hint": "Du chasch Quick Add Magic verwendä",
|
||||||
|
|
|
@ -783,7 +783,8 @@
|
||||||
"days": "Days",
|
"days": "Days",
|
||||||
"weeks": "Weeks",
|
"weeks": "Weeks",
|
||||||
"months": "Months",
|
"months": "Months",
|
||||||
"years": "Years"
|
"years": "Years",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "You can use Quick Add Magic",
|
"hint": "You can use Quick Add Magic",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Days",
|
"days": "Days",
|
||||||
"weeks": "Weeks",
|
"weeks": "Weeks",
|
||||||
"months": "Months",
|
"months": "Months",
|
||||||
"years": "Years"
|
"years": "Years",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "You can use Quick Add Magic",
|
"hint": "You can use Quick Add Magic",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Jours",
|
"days": "Jours",
|
||||||
"weeks": "Semaines",
|
"weeks": "Semaines",
|
||||||
"months": "Mois",
|
"months": "Mois",
|
||||||
"years": "Années"
|
"years": "Années",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "Tu peux utiliser Quick Add Magic",
|
"hint": "Tu peux utiliser Quick Add Magic",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Giorni",
|
"days": "Giorni",
|
||||||
"weeks": "Settimane",
|
"weeks": "Settimane",
|
||||||
"months": "Mesi",
|
"months": "Mesi",
|
||||||
"years": "Anni"
|
"years": "Anni",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "Puoi usare l'Aggiunta Rapida Magica",
|
"hint": "Puoi usare l'Aggiunta Rapida Magica",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Dagen",
|
"days": "Dagen",
|
||||||
"weeks": "Weken",
|
"weeks": "Weken",
|
||||||
"months": "Maanden",
|
"months": "Maanden",
|
||||||
"years": "Jaren"
|
"years": "Jaren",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "You can use Quick Add Magic",
|
"hint": "You can use Quick Add Magic",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Dni",
|
"days": "Dni",
|
||||||
"weeks": "Tygodnie",
|
"weeks": "Tygodnie",
|
||||||
"months": "Miesiące",
|
"months": "Miesiące",
|
||||||
"years": "Lata"
|
"years": "Lata",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "Możesz użyć Quick Add Magic",
|
"hint": "Możesz użyć Quick Add Magic",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Days",
|
"days": "Days",
|
||||||
"weeks": "Weeks",
|
"weeks": "Weeks",
|
||||||
"months": "Months",
|
"months": "Months",
|
||||||
"years": "Years"
|
"years": "Years",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "You can use Quick Add Magic",
|
"hint": "You can use Quick Add Magic",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Dias",
|
"days": "Dias",
|
||||||
"weeks": "Semanas",
|
"weeks": "Semanas",
|
||||||
"months": "Meses",
|
"months": "Meses",
|
||||||
"years": "Anos"
|
"years": "Anos",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "Podes utilizar a Introdução Mágica Rápida",
|
"hint": "Podes utilizar a Introdução Mágica Rápida",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Days",
|
"days": "Days",
|
||||||
"weeks": "Weeks",
|
"weeks": "Weeks",
|
||||||
"months": "Months",
|
"months": "Months",
|
||||||
"years": "Years"
|
"years": "Years",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "You can use Quick Add Magic",
|
"hint": "You can use Quick Add Magic",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Дней",
|
"days": "Дней",
|
||||||
"weeks": "Недель",
|
"weeks": "Недель",
|
||||||
"months": "Месяцев",
|
"months": "Месяцев",
|
||||||
"years": "Лет"
|
"years": "Лет",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "Вы можете использовать Волшебное Быстрое Добавление",
|
"hint": "Вы можете использовать Волшебное Быстрое Добавление",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Days",
|
"days": "Days",
|
||||||
"weeks": "Weeks",
|
"weeks": "Weeks",
|
||||||
"months": "Months",
|
"months": "Months",
|
||||||
"years": "Years"
|
"years": "Years",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "You can use Quick Add Magic",
|
"hint": "You can use Quick Add Magic",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Days",
|
"days": "Days",
|
||||||
"weeks": "Weeks",
|
"weeks": "Weeks",
|
||||||
"months": "Months",
|
"months": "Months",
|
||||||
"years": "Years"
|
"years": "Years",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "You can use Quick Add Magic",
|
"hint": "You can use Quick Add Magic",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Days",
|
"days": "Days",
|
||||||
"weeks": "Weeks",
|
"weeks": "Weeks",
|
||||||
"months": "Months",
|
"months": "Months",
|
||||||
"years": "Years"
|
"years": "Years",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "You can use Quick Add Magic",
|
"hint": "You can use Quick Add Magic",
|
||||||
|
|
|
@ -780,7 +780,8 @@
|
||||||
"days": "Ngày",
|
"days": "Ngày",
|
||||||
"weeks": "Tuần",
|
"weeks": "Tuần",
|
||||||
"months": "Tháng",
|
"months": "Tháng",
|
||||||
"years": "Năm"
|
"years": "Năm",
|
||||||
|
"invalidAmount": "Please enter more than 0."
|
||||||
},
|
},
|
||||||
"quickAddMagic": {
|
"quickAddMagic": {
|
||||||
"hint": "Bạn có thể sử dụng Quick Add Magic",
|
"hint": "Bạn có thể sử dụng Quick Add Magic",
|
||||||
|
|
|
@ -314,6 +314,42 @@ describe('Parse Task Text', () => {
|
||||||
expect(result.text).toBe('Lorem Ipsum github')
|
expect(result.text).toBe('Lorem Ipsum github')
|
||||||
expect(result.date).toBeNull()
|
expect(result.date).toBeNull()
|
||||||
})
|
})
|
||||||
|
describe('Should not recognize weekdays in words', () => {
|
||||||
|
const cases = [
|
||||||
|
'renewed',
|
||||||
|
'github',
|
||||||
|
'fix monitor stand',
|
||||||
|
'order wedding cake',
|
||||||
|
'investigate thumping noise',
|
||||||
|
'iron frilly napkins',
|
||||||
|
'take photo of saturn',
|
||||||
|
'fix sunglasses',
|
||||||
|
'monitor blood pressure',
|
||||||
|
'Monitor blood pressure',
|
||||||
|
'buy almonds',
|
||||||
|
]
|
||||||
|
|
||||||
|
cases.forEach(c => {
|
||||||
|
it(`should not recognize text with ${c} at the beginning as weekday`, () => {
|
||||||
|
const result = parseTaskText(`${c} dolor sit amet`)
|
||||||
|
|
||||||
|
expect(result.text).toBe(`${c} dolor sit amet`)
|
||||||
|
expect(result.date).toBeNull()
|
||||||
|
})
|
||||||
|
it(`should not recognize text with ${c} at the end as weekday`, () => {
|
||||||
|
const result = parseTaskText(`Lorem Ipsum ${c}`)
|
||||||
|
|
||||||
|
expect(result.text).toBe(`Lorem Ipsum ${c}`)
|
||||||
|
expect(result.date).toBeNull()
|
||||||
|
})
|
||||||
|
it(`should not recognize text with ${c} as weekday`, () => {
|
||||||
|
const result = parseTaskText(`Lorem Ipsum ${c} dolor`)
|
||||||
|
|
||||||
|
expect(result.text).toBe(`Lorem Ipsum ${c} dolor`)
|
||||||
|
expect(result.date).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
it('should not recognize date number with no spacing around them', () => {
|
it('should not recognize date number with no spacing around them', () => {
|
||||||
const result = parseTaskText('Lorem Ispum v1.1.1')
|
const result = parseTaskText('Lorem Ispum v1.1.1')
|
||||||
|
|
||||||
|
|
|
@ -318,7 +318,7 @@ export default {
|
||||||
const oldBucket = cloneDeep(ctx.state.buckets[bucketIndex])
|
const oldBucket = cloneDeep(ctx.state.buckets[bucketIndex])
|
||||||
|
|
||||||
const updatedBucket = {
|
const updatedBucket = {
|
||||||
...ctx.state.buckets[bucketIndex],
|
...oldBucket,
|
||||||
...updatedBucketData,
|
...updatedBucketData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ import LabelTask from '@/models/labelTask'
|
||||||
import LabelModel from '@/models/label'
|
import LabelModel from '@/models/label'
|
||||||
import UserService from '@/services/user'
|
import UserService from '@/services/user'
|
||||||
|
|
||||||
|
|
||||||
// IDEA: maybe use a small fuzzy search here to prevent errors
|
// IDEA: maybe use a small fuzzy search here to prevent errors
|
||||||
function findPropertyByValue(object, key, value) {
|
function findPropertyByValue(object, key, value) {
|
||||||
return Object.values(object).find(
|
return Object.values(object).find(
|
||||||
|
@ -286,12 +285,13 @@ export default {
|
||||||
return foundListId
|
return foundListId
|
||||||
},
|
},
|
||||||
|
|
||||||
async createNewTask({dispatch}, {
|
async createNewTask({dispatch, commit}, {
|
||||||
title,
|
title,
|
||||||
bucketId,
|
bucketId,
|
||||||
listId,
|
listId,
|
||||||
position,
|
position,
|
||||||
}) {
|
}) {
|
||||||
|
const cancel = setLoading({commit}, 'tasks')
|
||||||
const parsedTask = parseTaskText(title, getQuickAddMagicMode())
|
const parsedTask = parseTaskText(title, getQuickAddMagicMode())
|
||||||
|
|
||||||
const foundListId = await dispatch('findListId', {
|
const foundListId = await dispatch('findListId', {
|
||||||
|
@ -321,10 +321,12 @@ export default {
|
||||||
|
|
||||||
const taskService = new TaskService()
|
const taskService = new TaskService()
|
||||||
const createdTask = await taskService.create(task)
|
const createdTask = await taskService.create(task)
|
||||||
return dispatch('addLabelsToTask', {
|
const result = await dispatch('addLabelsToTask', {
|
||||||
task: createdTask,
|
task: createdTask,
|
||||||
parsedLabels: parsedTask.labels,
|
parsedLabels: parsedTask.labels,
|
||||||
})
|
})
|
||||||
|
cancel()
|
||||||
|
return result
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
|
@ -24,9 +24,6 @@ $vikunja-font: 'Quicksand', sans-serif;
|
||||||
$pagination-current-border: var(--primary);
|
$pagination-current-border: var(--primary);
|
||||||
$navbar-item-active-color: var(--primary);
|
$navbar-item-active-color: var(--primary);
|
||||||
|
|
||||||
$dropdown-content-shadow: none;
|
|
||||||
$dropdown-item-hover-background-color: var(--grey-100);
|
|
||||||
|
|
||||||
$site-background: var(--grey-100);
|
$site-background: var(--grey-100);
|
||||||
|
|
||||||
$transition-duration: 150ms;
|
$transition-duration: 150ms;
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
// imports from "bulma-css-variables/sass/components/_all";
|
// imports from "bulma-css-variables/sass/components/_all";
|
||||||
// @import "bulma-css-variables/sass/components/breadcrumb"; // not used
|
// @import "bulma-css-variables/sass/components/breadcrumb"; // not used
|
||||||
@import "bulma-css-variables/sass/components/card";
|
@import "bulma-css-variables/sass/components/card";
|
||||||
@import "bulma-css-variables/sass/components/dropdown";
|
// @import "bulma-css-variables/sass/components/dropdown"; // moved to component
|
||||||
// @import "bulma-css-variables/sass/components/level"; // not used
|
// @import "bulma-css-variables/sass/components/level"; // not used
|
||||||
@import "bulma-css-variables/sass/components/media";
|
@import "bulma-css-variables/sass/components/media";
|
||||||
@import "bulma-css-variables/sass/components/menu";
|
@import "bulma-css-variables/sass/components/menu";
|
||||||
|
|
|
@ -91,34 +91,6 @@ button.table {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: merge with dropdown-item.vue
|
|
||||||
// for this to happen the component has to be used everywhere
|
|
||||||
.dropdown-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: left !important;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
padding-right: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon:not(.has-text-success) {
|
|
||||||
color: var(--grey-300) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.has-text-danger .icon {
|
|
||||||
color: var(--danger) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-max-width-desktop {
|
.is-max-width-desktop {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: $desktop;
|
max-width: $desktop;
|
||||||
|
|
|
@ -76,12 +76,16 @@ const welcome = useDateTimeSalutation()
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const listHistory = computed(() => {
|
const listHistory = computed(() => {
|
||||||
|
// If we don't check this, it tries to load the list background right after logging out
|
||||||
|
if(!store.state.auth.authenticated) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
return getHistory()
|
return getHistory()
|
||||||
.map(l => store.getters['lists/getListById'](l.id))
|
.map(l => store.getters['lists/getListById'](l.id))
|
||||||
.filter(l => l !== null)
|
.filter(l => l !== null)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const migratorsEnabled = computed(() => store.state.config.availableMigrators?.length > 0)
|
const migratorsEnabled = computed(() => store.state.config.availableMigrators?.length > 0)
|
||||||
const userInfo = computed(() => store.state.auth.info)
|
const userInfo = computed(() => store.state.auth.info)
|
||||||
const hasTasks = computed(() => store.state.hasTasks)
|
const hasTasks = computed(() => store.state.hasTasks)
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
@start="() => dragBucket = true"
|
@start="() => dragBucket = true"
|
||||||
group="buckets"
|
group="buckets"
|
||||||
:disabled="!canWrite"
|
:disabled="!canWrite"
|
||||||
tag="transition-group"
|
tag="ul"
|
||||||
:item-key="({id}) => `bucket${id}`"
|
:item-key="({id}) => `bucket${id}`"
|
||||||
:component-data="bucketDraggableComponentData"
|
:component-data="bucketDraggableComponentData"
|
||||||
>
|
>
|
||||||
|
@ -62,9 +62,8 @@
|
||||||
trigger-icon="ellipsis-v"
|
trigger-icon="ellipsis-v"
|
||||||
@close="() => showSetLimitInput = false"
|
@close="() => showSetLimitInput = false"
|
||||||
>
|
>
|
||||||
<a
|
<dropdown-item
|
||||||
@click.stop="showSetLimitInput = true"
|
@click.stop="showSetLimitInput = true"
|
||||||
class="dropdown-item"
|
|
||||||
>
|
>
|
||||||
<div class="field has-addons" v-if="showSetLimitInput">
|
<div class="field has-addons" v-if="showSetLimitInput">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
@ -93,34 +92,32 @@
|
||||||
$t('list.kanban.limit', {limit: bucket.limit > 0 ? bucket.limit : $t('list.kanban.noLimit')})
|
$t('list.kanban.limit', {limit: bucket.limit > 0 ? bucket.limit : $t('list.kanban.noLimit')})
|
||||||
}}
|
}}
|
||||||
</template>
|
</template>
|
||||||
</a>
|
</dropdown-item>
|
||||||
<a
|
<dropdown-item
|
||||||
@click.stop="toggleDoneBucket(bucket)"
|
@click.stop="toggleDoneBucket(bucket)"
|
||||||
class="dropdown-item"
|
|
||||||
v-tooltip="$t('list.kanban.doneBucketHintExtended')"
|
v-tooltip="$t('list.kanban.doneBucketHintExtended')"
|
||||||
>
|
>
|
||||||
<span class="icon is-small" :class="{'has-text-success': bucket.isDoneBucket}">
|
<span class="icon is-small" :class="{'has-text-success': bucket.isDoneBucket}">
|
||||||
<icon icon="check-double"/>
|
<icon icon="check-double"/>
|
||||||
</span>
|
</span>
|
||||||
{{ $t('list.kanban.doneBucket') }}
|
{{ $t('list.kanban.doneBucket') }}
|
||||||
</a>
|
</dropdown-item>
|
||||||
<a
|
<dropdown-item
|
||||||
class="dropdown-item"
|
|
||||||
@click.stop="() => collapseBucket(bucket)"
|
@click.stop="() => collapseBucket(bucket)"
|
||||||
>
|
>
|
||||||
{{ $t('list.kanban.collapse') }}
|
{{ $t('list.kanban.collapse') }}
|
||||||
</a>
|
</dropdown-item>
|
||||||
<a
|
<dropdown-item
|
||||||
:class="{'is-disabled': buckets.length <= 1}"
|
:class="{'is-disabled': buckets.length <= 1}"
|
||||||
@click.stop="() => deleteBucketModal(bucket.id)"
|
@click.stop="() => deleteBucketModal(bucket.id)"
|
||||||
class="dropdown-item has-text-danger"
|
class="has-text-danger"
|
||||||
v-tooltip="buckets.length <= 1 ? $t('list.kanban.deleteLast') : ''"
|
v-tooltip="buckets.length <= 1 ? $t('list.kanban.deleteLast') : ''"
|
||||||
>
|
>
|
||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
<icon icon="trash-alt"/>
|
<icon icon="trash-alt"/>
|
||||||
</span>
|
</span>
|
||||||
{{ $t('misc.delete') }}
|
{{ $t('misc.delete') }}
|
||||||
</a>
|
</dropdown-item>
|
||||||
</dropdown>
|
</dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -133,17 +130,17 @@
|
||||||
:group="{name: 'tasks', put: shouldAcceptDrop(bucket) && !dragBucket}"
|
:group="{name: 'tasks', put: shouldAcceptDrop(bucket) && !dragBucket}"
|
||||||
:disabled="!canWrite"
|
:disabled="!canWrite"
|
||||||
:data-bucket-index="bucketIndex"
|
:data-bucket-index="bucketIndex"
|
||||||
tag="transition-group"
|
tag="ul"
|
||||||
:item-key="(task) => `bucket${bucket.id}-task${task.id}`"
|
:item-key="(task) => `bucket${bucket.id}-task${task.id}`"
|
||||||
:component-data="getTaskDraggableTaskComponentData(bucket)"
|
:component-data="getTaskDraggableTaskComponentData(bucket)"
|
||||||
>
|
>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="bucket-footer" v-if="canWrite">
|
<div class="bucket-footer" v-if="canWrite">
|
||||||
<div class="field" v-if="showNewTaskInput[bucket.id]">
|
<div class="field" v-if="showNewTaskInput[bucket.id]">
|
||||||
<div class="control" :class="{'is-loading': loading}">
|
<div class="control" :class="{'is-loading': loading || taskLoading}">
|
||||||
<input
|
<input
|
||||||
class="input"
|
class="input"
|
||||||
:disabled="loading || undefined"
|
:disabled="loading || taskLoading || undefined"
|
||||||
@focusout="toggleShowNewTaskInput(bucket.id)"
|
@focusout="toggleShowNewTaskInput(bucket.id)"
|
||||||
@keyup.enter="addTaskToBucket(bucket.id)"
|
@keyup.enter="addTaskToBucket(bucket.id)"
|
||||||
@keyup.esc="toggleShowNewTaskInput(bucket.id)"
|
@keyup.esc="toggleShowNewTaskInput(bucket.id)"
|
||||||
|
@ -172,7 +169,7 @@
|
||||||
|
|
||||||
<template #item="{element: task}">
|
<template #item="{element: task}">
|
||||||
<div class="task-item">
|
<div class="task-item">
|
||||||
<kanban-card class="kanban-card" :task="task"/>
|
<kanban-card class="kanban-card" :task="task" :loading="taskUpdating[task.id] ?? false"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
|
@ -241,6 +238,7 @@ import Dropdown from '@/components/misc/dropdown.vue'
|
||||||
import {getCollapsedBucketState, saveCollapsedBucketState} from '@/helpers/saveCollapsedBucketState'
|
import {getCollapsedBucketState, saveCollapsedBucketState} from '@/helpers/saveCollapsedBucketState'
|
||||||
import {calculateItemPosition} from '../../helpers/calculateItemPosition'
|
import {calculateItemPosition} from '../../helpers/calculateItemPosition'
|
||||||
import KanbanCard from '@/components/tasks/partials/kanban-card.vue'
|
import KanbanCard from '@/components/tasks/partials/kanban-card.vue'
|
||||||
|
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||||
|
|
||||||
const DRAG_OPTIONS = {
|
const DRAG_OPTIONS = {
|
||||||
// sortable options
|
// sortable options
|
||||||
|
@ -256,6 +254,7 @@ const MIN_SCROLL_HEIGHT_PERCENT = 0.25
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Kanban',
|
name: 'Kanban',
|
||||||
components: {
|
components: {
|
||||||
|
DropdownItem,
|
||||||
ListWrapper,
|
ListWrapper,
|
||||||
KanbanCard,
|
KanbanCard,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
|
@ -316,8 +315,7 @@ export default defineComponent({
|
||||||
return (bucket) => ({
|
return (bucket) => ({
|
||||||
ref: (el) => this.setTaskContainerRef(bucket.id, el),
|
ref: (el) => this.setTaskContainerRef(bucket.id, el),
|
||||||
onScroll: (event) => this.handleTaskContainerScroll(bucket.id, bucket.listId, event.target),
|
onScroll: (event) => this.handleTaskContainerScroll(bucket.id, bucket.listId, event.target),
|
||||||
type: 'transition',
|
type: 'transition-group',
|
||||||
tag: 'div',
|
|
||||||
name: !this.drag ? 'move-card' : null,
|
name: !this.drag ? 'move-card' : null,
|
||||||
class: [
|
class: [
|
||||||
'tasks',
|
'tasks',
|
||||||
|
@ -337,8 +335,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
bucketDraggableComponentData() {
|
bucketDraggableComponentData() {
|
||||||
return {
|
return {
|
||||||
type: 'transition',
|
type: 'transition-group',
|
||||||
tag: 'div',
|
|
||||||
name: !this.dragBucket ? 'move-bucket' : null,
|
name: !this.dragBucket ? 'move-bucket' : null,
|
||||||
class: [
|
class: [
|
||||||
'kanban-bucket-container',
|
'kanban-bucket-container',
|
||||||
|
@ -426,6 +423,7 @@ export default defineComponent({
|
||||||
const task = newBucket.tasks[newTaskIndex]
|
const task = newBucket.tasks[newTaskIndex]
|
||||||
const taskBefore = newBucket.tasks[newTaskIndex - 1] ?? null
|
const taskBefore = newBucket.tasks[newTaskIndex - 1] ?? null
|
||||||
const taskAfter = newBucket.tasks[newTaskIndex + 1] ?? null
|
const taskAfter = newBucket.tasks[newTaskIndex + 1] ?? null
|
||||||
|
this.taskUpdating[task.id] = true
|
||||||
|
|
||||||
const newTask = cloneDeep(task) // cloning the task to avoid vuex store mutations
|
const newTask = cloneDeep(task) // cloning the task to avoid vuex store mutations
|
||||||
newTask.bucketId = newBucket.id
|
newTask.bucketId = newBucket.id
|
||||||
|
@ -438,8 +436,7 @@ export default defineComponent({
|
||||||
await this.$store.dispatch('tasks/update', newTask)
|
await this.$store.dispatch('tasks/update', newTask)
|
||||||
|
|
||||||
// Make sure the first and second task don't both get position 0 assigned
|
// Make sure the first and second task don't both get position 0 assigned
|
||||||
if(newTaskIndex === 0 && taskAfter.kanbanPosition === 0) {
|
if(newTaskIndex === 0 && taskAfter !== null && taskAfter.kanbanPosition === 0) {
|
||||||
console.log('first', taskAfter.id, taskAfter.kanbanPosition)
|
|
||||||
const taskAfterAfter = newBucket.tasks[newTaskIndex + 2] ?? null
|
const taskAfterAfter = newBucket.tasks[newTaskIndex + 2] ?? null
|
||||||
const newTaskAfter = cloneDeep(taskAfter) // cloning the task to avoid vuex store mutations
|
const newTaskAfter = cloneDeep(taskAfter) // cloning the task to avoid vuex store mutations
|
||||||
newTaskAfter.bucketId = newBucket.id
|
newTaskAfter.bucketId = newBucket.id
|
||||||
|
@ -730,10 +727,6 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a.dropdown-item {
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-collapsed {
|
&.is-collapsed {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
transform: rotate(90deg) translateY(-100%);
|
transform: rotate(90deg) translateY(-100%);
|
||||||
|
|
|
@ -84,14 +84,17 @@
|
||||||
handle=".handle"
|
handle=".handle"
|
||||||
:disabled="!canWrite"
|
:disabled="!canWrite"
|
||||||
item-key="id"
|
item-key="id"
|
||||||
|
tag="ul"
|
||||||
:component-data="{
|
:component-data="{
|
||||||
class: { 'dragging-disabled': !canWrite || isAlphabeticalSorting },
|
class: { 'dragging-disabled': !canWrite || isAlphabeticalSorting },
|
||||||
|
type: 'transition-group'
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #item="{element: t}">
|
<template #item="{element: t}">
|
||||||
<single-task-in-list
|
<single-task-in-list
|
||||||
:show-list-color="false"
|
:show-list-color="false"
|
||||||
:disabled="!canWrite"
|
:disabled="!canWrite"
|
||||||
|
:can-mark-as-done="canWrite || (list.isSavedFilter && list.isSavedFilter())"
|
||||||
:the-task="t"
|
:the-task="t"
|
||||||
@taskUpdated="updateTasks"
|
@taskUpdated="updateTasks"
|
||||||
>
|
>
|
||||||
|
|
|
@ -21,9 +21,9 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, shallowReactive} from 'vue'
|
import {ref, shallowReactive} from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import {useRoute, useRouter} from 'vue-router'
|
||||||
import { useStore } from 'vuex'
|
import {useStore} from 'vuex'
|
||||||
import { useI18n } from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
import ListDuplicateService from '@/services/listDuplicateService'
|
import ListDuplicateService from '@/services/listDuplicateService'
|
||||||
import CreateEdit from '@/components/misc/create-edit.vue'
|
import CreateEdit from '@/components/misc/create-edit.vue'
|
||||||
|
@ -32,29 +32,30 @@ import Multiselect from '@/components/input/multiselect.vue'
|
||||||
import ListDuplicateModel from '@/models/listDuplicateModel'
|
import ListDuplicateModel from '@/models/listDuplicateModel'
|
||||||
import NamespaceModel from '@/models/namespace'
|
import NamespaceModel from '@/models/namespace'
|
||||||
|
|
||||||
import { success } from '@/message'
|
import {success} from '@/message'
|
||||||
import { useTitle } from '@/composables/useTitle'
|
import {useTitle} from '@/composables/useTitle'
|
||||||
import { useNameSpaceSearch } from '@/composables/useNamespaceSearch'
|
import {useNameSpaceSearch} from '@/composables/useNamespaceSearch'
|
||||||
|
|
||||||
|
const {t} = useI18n({useScope: 'global'})
|
||||||
const { t } = useI18n({useScope: 'global'})
|
|
||||||
useTitle(() => t('list.duplicate.title'))
|
useTitle(() => t('list.duplicate.title'))
|
||||||
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
namespaces,
|
namespaces,
|
||||||
findNamespaces,
|
findNamespaces,
|
||||||
} = useNameSpaceSearch()
|
} = useNameSpaceSearch()
|
||||||
|
|
||||||
const selectedNamespace = ref<NamespaceModel>()
|
const selectedNamespace = ref<NamespaceModel>()
|
||||||
|
|
||||||
function selectNamespace(namespace: NamespaceModel) {
|
function selectNamespace(namespace: NamespaceModel) {
|
||||||
selectedNamespace.value = namespace
|
selectedNamespace.value = namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router= useRouter()
|
const router = useRouter()
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
const listDuplicateService = shallowReactive(new ListDuplicateService())
|
const listDuplicateService = shallowReactive(new ListDuplicateService())
|
||||||
|
|
||||||
async function duplicateList() {
|
async function duplicateList() {
|
||||||
const listDuplicate = new ListDuplicateModel({
|
const listDuplicate = new ListDuplicateModel({
|
||||||
// FIXME: should be parameter
|
// FIXME: should be parameter
|
||||||
|
@ -64,7 +65,6 @@ async function duplicateList() {
|
||||||
|
|
||||||
const duplicate = await listDuplicateService.create(listDuplicate)
|
const duplicate = await listDuplicateService.create(listDuplicate)
|
||||||
|
|
||||||
const store = useStore()
|
|
||||||
store.commit('namespaces/addListToNamespace', duplicate.list)
|
store.commit('namespaces/addListToNamespace', duplicate.list)
|
||||||
store.commit('lists/setList', duplicate.list)
|
store.commit('lists/setList', duplicate.list)
|
||||||
success({message: t('list.duplicate.success')})
|
success({message: t('list.duplicate.success')})
|
||||||
|
|
|
@ -154,9 +154,19 @@
|
||||||
<transition name="flash-background" appear>
|
<transition name="flash-background" appear>
|
||||||
<div class="column" v-if="activeFields.repeatAfter">
|
<div class="column" v-if="activeFields.repeatAfter">
|
||||||
<!-- Repeat after -->
|
<!-- Repeat after -->
|
||||||
<div class="detail-title">
|
<div class="is-flex is-justify-content-space-between">
|
||||||
<icon icon="history"/>
|
<div class="detail-title">
|
||||||
{{ $t('task.attributes.repeat') }}
|
<icon icon="history"/>
|
||||||
|
{{ $t('task.attributes.repeat') }}
|
||||||
|
</div>
|
||||||
|
<BaseButton
|
||||||
|
@click="() => {task.repeatAfter.amount = 0;saveTask()}"
|
||||||
|
v-if="canWrite"
|
||||||
|
class="remove">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<icon icon="times"></icon>
|
||||||
|
</span>
|
||||||
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
<repeat-after
|
<repeat-after
|
||||||
:disabled="!canWrite"
|
:disabled="!canWrite"
|
||||||
|
@ -396,20 +406,18 @@
|
||||||
<created-updated :task="task" v-if="!canWrite && !shouldShowClosePopup"/>
|
<created-updated :task="task" v-if="!canWrite && !shouldShowClosePopup"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition name="modal">
|
<modal
|
||||||
<modal
|
@close="showDeleteModal = false"
|
||||||
@close="showDeleteModal = false"
|
@submit="deleteTask()"
|
||||||
@submit="deleteTask()"
|
v-if="showDeleteModal"
|
||||||
v-if="showDeleteModal"
|
>
|
||||||
>
|
<template #header><span>{{ $t('task.detail.delete.header') }}</span></template>
|
||||||
<template #header><span>{{ $t('task.detail.delete.header') }}</span></template>
|
|
||||||
|
|
||||||
<template #text>
|
<template #text>
|
||||||
<p>{{ $t('task.detail.delete.text1') }}<br/>
|
<p>{{ $t('task.detail.delete.text1') }}<br/>
|
||||||
{{ $t('task.detail.delete.text2') }}</p>
|
{{ $t('task.detail.delete.text2') }}</p>
|
||||||
</template>
|
</template>
|
||||||
</modal>
|
</modal>
|
||||||
</transition>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -770,13 +778,13 @@ $flash-background-duration: 750ms;
|
||||||
.date-input {
|
.date-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.remove {
|
.remove {
|
||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
padding-left: .5rem;
|
padding-left: .5rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.datepicker) {
|
:deep(.datepicker) {
|
||||||
|
|
|
@ -66,7 +66,7 @@ export default defineComponent({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, shallowReactive} from 'vue'
|
import {computed, ref, shallowReactive} from 'vue'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
import {useStore} from 'vuex'
|
import {useStore} from 'vuex'
|
||||||
import {Cropper} from 'vue-advanced-cropper'
|
import {Cropper} from 'vue-advanced-cropper'
|
||||||
|
@ -80,13 +80,13 @@ import { success } from '@/message'
|
||||||
const {t} = useI18n({useScope: 'global'})
|
const {t} = useI18n({useScope: 'global'})
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
const AVATAR_PROVIDERS = {
|
const AVATAR_PROVIDERS = computed(() => ({
|
||||||
default: t('misc.default'),
|
default: t('misc.default'),
|
||||||
initials: t('user.settings.avatar.initials'),
|
initials: t('user.settings.avatar.initials'),
|
||||||
gravatar: t('user.settings.avatar.gravatar'),
|
gravatar: t('user.settings.avatar.gravatar'),
|
||||||
marble: t('user.settings.avatar.marble'),
|
marble: t('user.settings.avatar.marble'),
|
||||||
upload: t('user.settings.avatar.upload'),
|
upload: t('user.settings.avatar.upload'),
|
||||||
}
|
}))
|
||||||
|
|
||||||
useTitle(() => `${t('user.settings.avatar.title')} - ${t('user.settings.title')}`)
|
useTitle(() => `${t('user.settings.avatar.title')} - ${t('user.settings.title')}`)
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,8 @@
|
||||||
v-for="lang in availableLanguageOptions"
|
v-for="lang in availableLanguageOptions"
|
||||||
:key="lang.code"
|
:key="lang.code"
|
||||||
:value="lang.code"
|
:value="lang.code"
|
||||||
>{{ lang.title }}</option>
|
>{{ lang.title }}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
@ -154,7 +155,7 @@ export default defineComponent({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed, watch, ref, reactive} from 'vue'
|
import {computed, watch, ref} from 'vue'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
import {useStore} from 'vuex'
|
import {useStore} from 'vuex'
|
||||||
|
|
||||||
|
@ -170,7 +171,8 @@ import {success} from '@/message'
|
||||||
import {AuthenticatedHTTPFactory} from '@/http-common'
|
import {AuthenticatedHTTPFactory} from '@/http-common'
|
||||||
|
|
||||||
import {useColorScheme} from '@/composables/useColorScheme'
|
import {useColorScheme} from '@/composables/useColorScheme'
|
||||||
import { useTitle } from '@/composables/useTitle'
|
import {useTitle} from '@/composables/useTitle'
|
||||||
|
import {objectIsEmpty} from '@/helpers/objectIsEmpty'
|
||||||
|
|
||||||
const {t} = useI18n({useScope: 'global'})
|
const {t} = useI18n({useScope: 'global'})
|
||||||
useTitle(() => `${t('user.settings.general.title')} - ${t('user.settings.title')}`)
|
useTitle(() => `${t('user.settings.general.title')} - ${t('user.settings.title')}`)
|
||||||
|
@ -213,6 +215,7 @@ function useAvailableTimezones() {
|
||||||
|
|
||||||
return availableTimezones
|
return availableTimezones
|
||||||
}
|
}
|
||||||
|
|
||||||
const availableTimezones = useAvailableTimezones()
|
const availableTimezones = useAvailableTimezones()
|
||||||
|
|
||||||
function getPlaySoundWhenDoneSetting() {
|
function getPlaySoundWhenDoneSetting() {
|
||||||
|
@ -223,7 +226,7 @@ const playSoundWhenDone = ref(getPlaySoundWhenDoneSetting())
|
||||||
const quickAddMagicMode = ref(getQuickAddMagicMode())
|
const quickAddMagicMode = ref(getQuickAddMagicMode())
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const settings = reactive({...store.state.auth.settings})
|
const settings = ref({...store.state.auth.settings})
|
||||||
const id = ref(createRandomID())
|
const id = ref(createRandomID())
|
||||||
const availableLanguageOptions = ref(
|
const availableLanguageOptions = ref(
|
||||||
Object.entries(availableLanguages)
|
Object.entries(availableLanguages)
|
||||||
|
@ -231,10 +234,22 @@ const availableLanguageOptions = ref(
|
||||||
.sort((a, b) => a.title.localeCompare(b.title)),
|
.sort((a, b) => a.title.localeCompare(b.title)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => store.state.auth.settings,
|
||||||
|
() => {
|
||||||
|
// Only setting if we don't have values set yet to avoid overriding edited values
|
||||||
|
if (!objectIsEmpty(settings.value)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
settings.value = {...store.state.auth.settings}
|
||||||
|
},
|
||||||
|
{immediate: true},
|
||||||
|
)
|
||||||
|
|
||||||
const defaultList = computed({
|
const defaultList = computed({
|
||||||
get: () => store.getters['lists/getListById'](settings.defaultListId),
|
get: () => store.getters['lists/getListById'](settings.value.defaultListId),
|
||||||
set(l) {
|
set(l) {
|
||||||
settings.defaultListId = l ? l.id : DEFAULT_LIST_ID
|
settings.value.defaultListId = l ? l.id : DEFAULT_LIST_ID
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const loading = computed(() => store.state.loading && store.state.loadingModule === 'general-settings')
|
const loading = computed(() => store.state.loading && store.state.loadingModule === 'general-settings')
|
||||||
|
@ -244,13 +259,12 @@ watch(
|
||||||
(play) => play && playPopSound(),
|
(play) => play && playPopSound(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async function updateSettings() {
|
async function updateSettings() {
|
||||||
localStorage.setItem(playSoundWhenDoneKey, playSoundWhenDone.value ? 'true' : 'false')
|
localStorage.setItem(playSoundWhenDoneKey, playSoundWhenDone.value ? 'true' : 'false')
|
||||||
setQuickAddMagicMode(quickAddMagicMode.value)
|
setQuickAddMagicMode(quickAddMagicMode.value)
|
||||||
|
|
||||||
await store.dispatch('auth/saveUserSettings', {
|
await store.dispatch('auth/saveUserSettings', {
|
||||||
settings: {...settings},
|
settings: {...settings.value},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user