Compare commits

...

59 Commits

Author SHA1 Message Date
7b17ccbf1f fix(deps): update sentry-javascript monorepo to v7.8.0 (#2208)
Reviewed-on: vikunja/frontend#2208
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 18:45:49 +00:00
ce7563ea4c fix(deps): update vueuse to v9 (major) (#2209)
Reviewed-on: vikunja/frontend#2209
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 17:39:53 +00:00
d9f3555d8d fix(deps): update dependency vue-router to v4.1.3 (#2206)
Reviewed-on: vikunja/frontend#2206
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 17:14:05 +00:00
b2dd63630c chore(deps): update typescript-eslint monorepo to v5.31.0 (#2207)
Reviewed-on: vikunja/frontend#2207
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 17:12:34 +00:00
db1a41f845 fix(deps): update dependency @kyvg/vue3-notification to v2.3.6 (#2205)
Reviewed-on: vikunja/frontend#2205
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 17:01:26 +00:00
08ae0046de chore(deps): update dependency vue-tsc to v0.39.4 (#2187)
Reviewed-on: vikunja/frontend#2187
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 16:21:19 +00:00
c173542b23 chore(deps): update dependency rollup to v2.77.2 (#2203)
Reviewed-on: vikunja/frontend#2203
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 16:19:32 +00:00
518417c0de
feat: add more testcases for parsing weekdays
Related to vikunja/api#1223
2022-07-31 18:16:31 +02:00
c2e58a2320 chore(deps): update dependency autoprefixer to v10.4.8 (#2202)
Reviewed-on: vikunja/frontend#2202
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 16:09:59 +00:00
d94a25c83f fix(deps): update dependency date-fns to v2.29.1 (#2185)
Reviewed-on: vikunja/frontend#2185
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 13:48:43 +00:00
ad9ca61969 chore(deps): update dependency sass to v1.54.0 (#2186)
Reviewed-on: vikunja/frontend#2186
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 13:48:12 +00:00
d28d9218bd chore(deps): update caniuse-and-related (#2189)
Reviewed-on: vikunja/frontend#2189
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 13:47:43 +00:00
af08713bf0 chore(deps): update dependency netlify-cli to v10.13.0 (#2190)
Reviewed-on: vikunja/frontend#2190
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 13:47:11 +00:00
0b01f2aace chore(deps): update dependency esbuild to v0.14.51 (#2191)
Reviewed-on: vikunja/frontend#2191
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 13:46:22 +00:00
579de70a7a chore(deps): update dependency eslint-plugin-vue to v9.3.0 (#2192)
Reviewed-on: vikunja/frontend#2192
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 13:45:46 +00:00
660ab928a2 chore(deps): update dependency vite to v3.0.4 (#2193)
Reviewed-on: vikunja/frontend#2193
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 13:44:53 +00:00
9394f57fc9 chore(deps): update font awesome to v6.1.2 (#2198)
Reviewed-on: vikunja/frontend#2198
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-31 13:44:12 +00:00
873169c371 chore(deps): update dependency vitest to v0.20.2 (#2188)
Reviewed-on: vikunja/frontend#2188
2022-07-31 13:43:34 +00:00
098fd0a875 chore(deps): update dependency vitest to v0.20.2 2022-07-31 12:02:53 +00:00
c0cd69dd82 [skip ci] Updated translations via Crowdin 2022-07-23 00:15:10 +00:00
4666087aa9
feat: add issue template
Resolves vikunja/frontend#2177

(cherry picked from commit 5442306b7cf2b1e2e68c57cb9b3a464c31565fe6)
2022-07-21 16:49:21 +02:00
56147dc9fb
fix: transition error when deleting a task 2022-07-21 16:14:12 +02:00
ff48178051
fix: general user settings empty when loading the settings page
Resolves vikunja/frontend#2183
2022-07-21 16:11:45 +02:00
cb3f269937
fix(kanban): reset loading state after creating a task
Resolves: vikunja/frontend#2184
2022-07-21 15:57:03 +02:00
4c560f1a03
fix: don't try to load lists after logging out
Resolves vikunja/frontend#329
2022-07-21 15:53:15 +02:00
0fe2a16a7c fix(deps): update dependency @github/hotkey to v2.0.1 (#2182)
Reviewed-on: vikunja/frontend#2182
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-21 13:35:54 +00:00
9cebf5305a
feat: allow for easy reset of a repeating amount
Resolves vikunja/frontend#2179
2022-07-21 15:10:48 +02:00
71c8540c74
fix: don't allow negative repeat amounts
Partial fix for vikunja/frontend#2179
2022-07-21 15:06:17 +02:00
8183fce829
fix: user menu dropdown
vikunja/frontend#2178
2022-07-21 14:08:14 +02:00
3becf8738b
fix: logo spacing for link shares
Resolves #1142
2022-07-21 01:00:21 +02:00
9ddb55a5ef
fix: vuex state mutation error when moving a kanban bucket 2022-07-20 17:18:27 +02:00
cdb63b578d
chore: use the <dropdown> and <dropdown-item> components everywhere
Resolves vikunja/frontend#2176
2022-07-20 17:08:58 +02:00
d6a10b01dd chore(deps): update dependency vue-tsc to v0.38.9 (#2162)
Reviewed-on: vikunja/frontend#2162
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-20 09:06:08 +00:00
b74c961723 chore(deps): update dependency cypress to v10.3.1 (#2175)
Reviewed-on: vikunja/frontend#2175
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-19 20:24:27 +00:00
8b0e88b574
fix: lint 2022-07-19 21:20:44 +02:00
175fb02629
fix: don't allow marking a task as done in a read-only list 2022-07-19 18:37:11 +02:00
dac9d918b5
feat(kanban): show loading indicators when handling tasks 2022-07-19 18:33:45 +02:00
e7de930129
fix: properly update state when duplicating a list 2022-07-19 17:15:39 +02:00
a0d0c2cb1f
fix(kanban): error when moving a task to an empty bucket 2022-07-19 17:13:22 +02:00
a4d3cafdf1
fix: pagination on table view should not open the list view
Resolves vikunja/frontend#2173
2022-07-19 17:11:11 +02:00
f5bb697032
fix: quick actions arrow key navigation in dark mode 2022-07-19 17:04:35 +02:00
62bbffb17e
fix: user avatar settings 2022-07-19 17:00:40 +02:00
c2d5370e4a
fix: don't use transitions for elements where it is not possible
Resolves vikunja/frontend#2153
2022-07-19 16:56:09 +02:00
6dc02c45dd
fix: don't try to pass nonexistant props to filters
Resolves vikunja/frontend#2152
2022-07-19 16:41:55 +02:00
Dominik Pschenitschni
0456f4a041 fix: use of sortable js with transition-group (#2160)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#2160
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2022-07-19 14:32:12 +00:00
c1dd20a30b fix(deps): update dependency vue-i18n to v9.2.0-beta.40 (#2172)
Reviewed-on: vikunja/frontend#2172
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-19 14:20:44 +00:00
acc7bf4305 fix(deps): update dependency dompurify to v2.3.10 (#2167)
Reviewed-on: vikunja/frontend#2167
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-18 20:36:42 +00:00
86688d7e95 chore(deps): update dependency vite to v3.0.2 (#2166)
Reviewed-on: vikunja/frontend#2166
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-18 20:36:11 +00:00
aaee49b70e chore(deps): update typescript-eslint monorepo to v5.30.7 (#2168)
Reviewed-on: vikunja/frontend#2168
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-18 20:35:44 +00:00
6b23b954a8 chore(deps): update dependency @vitejs/plugin-vue to v3.0.1 (#2147)
Reviewed-on: vikunja/frontend#2147
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-18 20:35:17 +00:00
9fd2f4ea5c
fix: datepicker confirm button overflow
Resolves #2165
2022-07-18 15:13:51 +02:00
dca05f852c chore(deps): update dependency vite to v3 (#2149)
Reviewed-on: vikunja/frontend#2149
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Reviewed-by: konrad <k@knt.li>
2022-07-18 13:05:36 +00:00
5aa6cce185
chore: update lockfile 2022-07-18 12:15:07 +02:00
d96ea384dc
fix: upgrade packages for vite 3.0 2022-07-18 12:13:28 +02:00
5d33144b8e
chore(deps): update dependency vite to v3 2022-07-18 12:13:25 +02:00
d462d56202 chore(deps): update dependency eslint to v8.20.0 (#2159)
Reviewed-on: vikunja/frontend#2159
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-18 10:10:19 +00:00
2648592ac0 fix(deps): update vueuse to v8.9.4 (#2161)
Reviewed-on: vikunja/frontend#2161
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-18 10:09:49 +00:00
1ac78729a4 fix(deps): update dependency vue-i18n to v9.2.0-beta.39 (#2163)
Reviewed-on: vikunja/frontend#2163
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-18 10:09:19 +00:00
9d1195a2ed chore(deps): update dependency happy-dom to v6.0.4 (#2164)
Reviewed-on: vikunja/frontend#2164
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-07-18 10:06:05 +00:00
52 changed files with 1110 additions and 700 deletions

44
.gitea/issue_template.md Normal file
View 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)

View File

@ -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"
}, },

View File

@ -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>

View File

@ -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'

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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'"

View File

@ -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 } }"

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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,
}, },

View File

@ -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)

View File

@ -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 } }"

View File

@ -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;
} }

View File

@ -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
} }
} }

View File

@ -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>

View 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
}

View File

@ -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",

View File

@ -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í",

View File

@ -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",

View File

@ -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ä",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -780,7 +780,8 @@
"days": "Дней", "days": "Дней",
"weeks": "Недель", "weeks": "Недель",
"months": "Месяцев", "months": "Месяцев",
"years": "Лет" "years": "Лет",
"invalidAmount": "Please enter more than 0."
}, },
"quickAddMagic": { "quickAddMagic": {
"hint": "Вы можете использовать Волшебное Быстрое Добавление", "hint": "Вы можете использовать Волшебное Быстрое Добавление",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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')

View File

@ -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,
} }

View File

@ -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
}, },
}, },
} }

View File

@ -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;

View File

@ -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";

View File

@ -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;

View File

@ -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)

View File

@ -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%);

View File

@ -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"
> >

View File

@ -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')})

View File

@ -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) {

View File

@ -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')}`)

View File

@ -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>

1116
yarn.lock

File diff suppressed because it is too large Load Diff