Compare commits

..

26 Commits

Author SHA1 Message Date
renovate c233c99ab7 fix(deps): update dependency @intlify/unplugin-vue-i18n to v4
continuous-integration/drone/pr Build is passing Details
2024-04-05 21:09:30 +00:00
renovate 90055d063c chore(deps): update dev-dependencies (#2229)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #2229
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-04-05 20:41:15 +00:00
waza-ari f0d695e789 fix(views): remove default filter from frontend, apply by default to new list views instead (#2240)
continuous-integration/drone/push Build is passing Details
Fixes #2234

Co-authored-by: Daniel Herrmann <daniel.herrmann1@gmail.com>
Reviewed-on: #2240
Reviewed-by: konrad <k@knt.li>
Co-authored-by: waza-ari <daniel.herrmann@makerspace-darmstadt.de>
Co-committed-by: waza-ari <daniel.herrmann@makerspace-darmstadt.de>
2024-04-02 13:49:38 +00:00
kolaente 95276ceebe
fix(reactions): do not enable reaction picker when the current user does not have write access
continuous-integration/drone/push Build is passing Details
2024-04-02 14:48:13 +02:00
kolaente 1558921f42
fix(test): use correct selector in Cypress test
continuous-integration/drone/push Build is failing Details
2024-04-02 14:31:15 +02:00
kolaente bf5088e546
fix(sharing): show user display name and avatar when displaying search results
continuous-integration/drone/push Build is failing Details
Resolves https://community.vikunja.io/t/autogenerated-username-using-google-openid/2183/12
2024-04-02 14:29:22 +02:00
kolaente 6f366d4907
feat(views): lint
continuous-integration/drone/push Build is failing Details
2024-04-02 14:04:17 +02:00
kolaente d7554d9e70
feat(views): hide view switcher when there is only one view 2024-04-02 14:02:59 +02:00
kolaente 8a72fe26f8
fix(views): refactor filter button slot in wrapper
Before this change, the filter button on the top right was positioned using absolute positioning and plenty of tricks, which were brittle and not really maintainable. Now, the buttons are positioned using flexbox, which should make this a lot more maintainable.
2024-04-02 14:02:31 +02:00
kolaente 13cab62d14
fix(views): transform view filter before and after loading it from the api
continuous-integration/drone/push Build is failing Details
Previously, the actual filter was kept as-is when sending it to the api, essentially creating an invalid filter. This change fixes this, transforming the filter before saving and after loading.

Resolves #2233
2024-04-02 13:20:17 +02:00
kolaente 81de986d8d
fix(gantt): correctly show day in chart 2024-04-02 12:53:14 +02:00
kolaente 915f677c2a
fix(views): correctly pass view id to wrapper when gantt view is active 2024-04-02 12:50:10 +02:00
kolaente 8a6e3d5bd7
fix(views): use correct assertion in test
continuous-integration/drone/push Build is passing Details
2024-04-02 12:42:07 +02:00
kolaente 81fe8391e4
fix(project): load full project after creating a project
continuous-integration/drone/push Build is failing Details
When a new project was created, it contained all details already. This led to duplicated views and overridden attributes in the response.

Resolves #2242
2024-03-29 19:28:17 +01:00
kolaente 89e37b88d9
fix(views): update all fields when updating a view
continuous-integration/drone/push Build is failing Details
Resolves #2241
2024-03-29 18:19:16 +01:00
kolaente cc6801c5b1
fix(filter): make sure highlight works for doneAt attribute
continuous-integration/drone/push Build is failing Details
2024-03-29 18:09:02 +01:00
kolaente 767b058915
fix(filter): add white background to filter input 2024-03-29 18:07:37 +01:00
kolaente 2c0d3f2885
fix(views): add bottom spacing 2024-03-29 18:05:30 +01:00
renovate fa170b9397 fix(deps): update sentry-javascript monorepo to v7.109.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-03-28 22:06:03 +00:00
kolaente a5fd6f834a
chore(deps): sign drone config
continuous-integration/drone/push Build is passing Details
2024-03-28 23:00:24 +01:00
renovate 8984e0e9f0 fix(deps): update module github.com/go-sql-driver/mysql to v1.8.1
continuous-integration/drone/pr Build is failing Details
continuous-integration/drone/push Build is failing Details
2024-03-28 21:06:31 +00:00
renovate 176c41dc40 fix(deps): update dependency express to v4.19.2
continuous-integration/drone/push Build is passing Details
2024-03-28 20:57:07 +00:00
waza-ari c4d3d99cd4 fix: pick first available view if currently configured view got deleted (#2235)
continuous-integration/drone/push Build is failing Details
Resolves #2232

Co-authored-by: Daniel Herrmann <daniel.herrmann1@gmail.com>
Reviewed-on: #2235
Reviewed-by: konrad <k@knt.li>
Co-authored-by: waza-ari <daniel.herrmann@makerspace-darmstadt.de>
Co-committed-by: waza-ari <daniel.herrmann@makerspace-darmstadt.de>
2024-03-28 20:55:52 +00:00
renovate 30b4ed6b23 fix(deps): update dependency dompurify to v3.0.11
continuous-integration/drone/push Build is failing Details
2024-03-28 20:53:00 +00:00
renovate aed92d1cd2 fix(deps): update sentry-javascript monorepo to v7.108.0
continuous-integration/drone/push Build is failing Details
2024-03-28 20:49:17 +00:00
Frederick [Bot] 2239d73797 chore(i18n): update translations via Crowdin
continuous-integration/drone/push Build is passing Details
2024-03-27 00:11:11 +00:00
31 changed files with 991 additions and 1158 deletions

View File

@ -461,7 +461,6 @@ steps:
- cp -r dist-test dist-preview
# Override the default api url used for preview
- sed -i 's|http://localhost:3456|https://try.vikunja.io|g' dist-preview/index.html
- apk add --no-cache perl-utils
# create via:
# `shasum -a 384 ./scripts/deploy-preview-netlify.mjs > ./scripts/deploy-preview-netlify.mjs.sha384`
- shasum -a 384 -c ./scripts/deploy-preview-netlify.mjs.sha384
@ -1401,6 +1400,6 @@ steps:
- failure
---
kind: signature
hmac: a5d31a6cb5eb6482e72bea619ee391ff2b8118b9865e3896a607b8a7e874a797
hmac: c312afe632177a2d45f47c429bf6c7528af3c51a097430956558532ccdcc42b9
...

View File

@ -51,11 +51,11 @@
}
},
"devDependencies": {
"electron": "29.1.4",
"electron": "29.2.0",
"electron-builder": "24.13.3"
},
"dependencies": {
"connect-history-api-fallback": "2.0.0",
"express": "4.19.0"
"express": "4.19.2"
}
}

View File

@ -769,10 +769,10 @@ electron-publish@24.13.1:
lazy-val "^1.0.5"
mime "^2.5.2"
electron@29.1.4:
version "29.1.4"
resolved "https://registry.yarnpkg.com/electron/-/electron-29.1.4.tgz#6c47467ba50be5dd60b99b8737f69cd12fc0733f"
integrity sha512-IWXys0SqgmIfrqXusUGQC0gGG7CCqA5vfmNsUMj8dFkAnK3lisKyjSESStWlrsste/OX/AAC5wsVlf23reUNnw==
electron@29.2.0:
version "29.2.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-29.2.0.tgz#98e9d45dcebda124fb0bd1ff20fc509ec692101c"
integrity sha512-ALKrCN52RG4g9prx4DriXSPnY5WoiyRUCNp7zEVQuoiNOpHTNqMMpRidQAHzntV4hajF1LMWHVoBkwqIs1jHhg==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^20.9.0"
@ -830,10 +830,10 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
express@4.19.0:
version "4.19.0"
resolved "https://registry.yarnpkg.com/express/-/express-4.19.0.tgz#c9f689a62522f3399132d49eacd9af177d8ccb9e"
integrity sha512-/ERliX0l7UuHEgAy7HU2FRsiz3ScIKNl/iwnoYzHTJC0Sqj3ctWDD3MQ9CbUEfjshvxXImWaeukD0Xo7a2lWLA==
express@4.19.2:
version "4.19.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"

View File

@ -21,7 +21,7 @@ describe('Project View Table', () => {
TaskFactory.create(1)
cy.visit('/projects/1/3')
cy.get('.project-table .filter-container .items .button')
cy.get('.project-table .filter-container .button')
.contains('Columns')
.click()
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox')

View File

@ -58,8 +58,8 @@
"@infectoone/vue-ganttastic": "2.3.1",
"@intlify/unplugin-vue-i18n": "4.0.0",
"@kyvg/vue3-notification": "3.2.1",
"@sentry/tracing": "7.107.0",
"@sentry/vue": "7.107.0",
"@sentry/tracing": "7.109.0",
"@sentry/vue": "7.109.0",
"@tiptap/core": "2.2.4",
"@tiptap/extension-blockquote": "2.2.4",
"@tiptap/extension-bold": "2.2.4",
@ -103,7 +103,7 @@
"camel-case": "4.1.2",
"date-fns": "3.6.0",
"dayjs": "1.11.10",
"dompurify": "3.0.10",
"dompurify": "3.0.11",
"fast-deep-equal": "3.1.3",
"flatpickr": "4.6.13",
"flexsearch": "0.7.31",
@ -132,54 +132,54 @@
"@cypress/vite-dev-server": "5.0.7",
"@cypress/vue": "6.0.0",
"@faker-js/faker": "8.4.1",
"@histoire/plugin-screenshot": "0.17.14",
"@histoire/plugin-vue": "0.17.14",
"@rushstack/eslint-patch": "1.8.0",
"@tsconfig/node18": "18.2.2",
"@histoire/plugin-screenshot": "0.17.15",
"@histoire/plugin-vue": "0.17.15",
"@rushstack/eslint-patch": "1.10.1",
"@tsconfig/node18": "18.2.4",
"@types/codemirror": "5.60.15",
"@types/dompurify": "3.0.5",
"@types/flexsearch": "0.7.6",
"@types/is-touch-device": "1.0.2",
"@types/lodash.debounce": "4.0.9",
"@types/marked": "5.0.2",
"@types/node": "20.11.30",
"@types/node": "20.12.4",
"@types/postcss-preset-env": "7.7.0",
"@types/sortablejs": "1.15.8",
"@typescript-eslint/eslint-plugin": "7.3.1",
"@typescript-eslint/parser": "7.3.1",
"@typescript-eslint/eslint-plugin": "7.5.0",
"@typescript-eslint/parser": "7.5.0",
"@vitejs/plugin-legacy": "5.3.2",
"@vitejs/plugin-vue": "5.0.4",
"@vue/eslint-config-typescript": "13.0.0",
"@vue/test-utils": "2.4.5",
"@vue/tsconfig": "0.5.1",
"autoprefixer": "10.4.18",
"autoprefixer": "10.4.19",
"browserslist": "4.23.0",
"caniuse-lite": "1.0.30001599",
"css-has-pseudo": "6.0.2",
"caniuse-lite": "1.0.30001606",
"css-has-pseudo": "6.0.3",
"csstype": "3.1.3",
"cypress": "13.7.0",
"cypress": "13.7.2",
"esbuild": "0.20.2",
"eslint": "8.57.0",
"eslint-plugin-vue": "9.23.0",
"happy-dom": "14.0.0",
"histoire": "0.17.14",
"postcss": "8.4.37",
"eslint-plugin-vue": "9.24.0",
"happy-dom": "14.3.2",
"histoire": "0.17.15",
"postcss": "8.4.38",
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "4.0.0",
"postcss-focus-within": "8.0.1",
"postcss-preset-env": "9.5.2",
"rollup": "4.13.0",
"postcss-preset-env": "9.5.4",
"rollup": "4.14.0",
"rollup-plugin-visualizer": "5.12.0",
"sass": "1.72.0",
"sass": "1.74.1",
"start-server-and-test": "2.0.3",
"typescript": "5.4.2",
"vite": "5.1.6",
"typescript": "5.4.4",
"vite": "5.2.8",
"vite-plugin-inject-preload": "1.3.3",
"vite-plugin-pwa": "0.19.5",
"vite-plugin-pwa": "0.19.8",
"vite-plugin-sentry": "1.4.0",
"vite-svg-loader": "5.1.0",
"vitest": "1.4.0",
"vue-tsc": "2.0.6",
"vue-tsc": "2.0.10",
"wait-on": "7.2.0",
"workbox-cli": "7.0.0"
},

File diff suppressed because it is too large Load Diff

View File

@ -16,9 +16,11 @@ import {useColorScheme} from '@/composables/useColorScheme'
const {
entityKind,
entityId,
disabled = false,
} = defineProps<{
entityKind: ReactionKind,
entityId: number,
disabled?: boolean,
}>()
const authStore = useAuthStore()
@ -143,11 +145,13 @@ async function toggleReaction(value: string) {
v-tooltip="getReactionTooltip(users, value)"
class="reaction-button"
:class="{'current-user-has-reacted': hasCurrentUserReactedWithEmoji(value)}"
:disabled
@click="toggleReaction(value)"
>
{{ value }} {{ users.length }}
</BaseButton>
<BaseButton
v-if="!disabled"
ref="emojiPickerButtonRef"
v-tooltip="$t('reaction.add')"
class="reaction-button"

View File

@ -13,7 +13,7 @@
}"
>
<template v-if="icon">
<icon
<icon
v-if="showIconOnly"
:icon="icon"
:style="{'color': iconColor !== '' ? iconColor : undefined}"
@ -22,7 +22,7 @@
v-else
class="icon is-small"
>
<icon
<icon
:icon="icon"
:style="{'color': iconColor !== '' ? iconColor : undefined}"
/>
@ -34,20 +34,20 @@
<script lang="ts">
const BUTTON_TYPES_MAP = {
primary: 'is-primary',
secondary: 'is-outlined',
tertiary: 'is-text is-inverted underline-none',
primary: 'is-primary',
secondary: 'is-outlined',
tertiary: 'is-text is-inverted underline-none',
} as const
export type ButtonTypes = keyof typeof BUTTON_TYPES_MAP
export default { name: 'XButton' }
export default {name: 'XButton'}
</script>
<script setup lang="ts">
import {computed, useSlots} from 'vue'
import BaseButton, {type BaseButtonProps} from '@/components/base/BaseButton.vue'
import type { IconProp } from '@fortawesome/fontawesome-svg-core'
import type {IconProp} from '@fortawesome/fontawesome-svg-core'
// extending the props of the BaseButton
export interface ButtonProps extends /* @vue-ignore */ BaseButtonProps {
@ -76,37 +76,38 @@ const showIconOnly = computed(() => icon !== '' && typeof slots.default === 'und
<style lang="scss" scoped>
.button {
transition: all $transition;
border: 0;
text-transform: uppercase;
font-size: 0.85rem;
font-weight: bold;
height: auto;
min-height: $button-height;
box-shadow: var(--shadow-sm);
display: inline-flex;
white-space: var(--button-white-space);
transition: all $transition;
border: 0;
text-transform: uppercase;
font-size: 0.85rem;
font-weight: bold;
height: auto;
min-height: $button-height;
box-shadow: var(--shadow-sm);
display: inline-flex;
white-space: var(--button-white-space);
line-height: 1;
&:hover {
box-shadow: var(--shadow-md);
}
&:hover {
box-shadow: var(--shadow-md);
}
&.fullheight {
padding-right: 7px;
height: 100%;
}
&.fullheight {
padding-right: 7px;
height: 100%;
}
&.is-active,
&.is-focused,
&:active,
&:focus,
&:focus:not(:active) {
box-shadow: var(--shadow-xs) !important;
}
&.is-active,
&.is-focused,
&:active,
&:focus,
&:focus:not(:active) {
box-shadow: var(--shadow-xs) !important;
}
&.is-primary.is-outlined:hover {
color: var(--white);
}
&.is-primary.is-outlined:hover {
color: var(--white);
}
}
.is-small {
@ -114,6 +115,6 @@ const showIconOnly = computed(() => icon !== '' && typeof slots.default === 'und
}
.underline-none {
text-decoration: none !important;
text-decoration: none !important;
}
</style>

View File

@ -7,8 +7,14 @@
{{ getProjectTitle(currentProject) }}
</h1>
<div class="switch-view-container d-print-none">
<div class="switch-view">
<div
class="switch-view-container d-print-none"
:class="{'is-justify-content-flex-end': views.length === 1}"
>
<div
v-if="views.length > 1"
class="switch-view"
>
<BaseButton
v-for="v in views"
:key="v.id"
@ -149,8 +155,14 @@ function getViewTitle(view: IProjectView) {
<style lang="scss" scoped>
.switch-view-container {
min-height: $switch-view-height;
margin-bottom: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
@media screen and (max-width: $tablet) {
display: flex;
justify-content: center;
flex-direction: column;
}
@ -162,8 +174,6 @@ function getViewTitle(view: IProjectView) {
border-radius: $radius;
font-size: .75rem;
box-shadow: var(--shadow-sm);
height: $switch-view-height;
margin: 0 auto 1rem;
padding: .5rem;
}

View File

@ -379,6 +379,7 @@ const blurDebounced = useDebounceFn(() => emit('blur'), 500)
}
.filter-input-highlight {
background: var(--white);
height: 2.5em;
line-height: 1.5;
padding: .5em .75em;

View File

@ -1,26 +1,10 @@
<!-- Vikunja is a to-do list application to facilitate your life. -->
<!-- Copyright 2018-present Vikunja and contributors. All rights reserved. -->
<!-- -->
<!-- This program is free software: you can redistribute it and/or modify -->
<!-- it under the terms of the GNU Affero General Public Licensee as published by -->
<!-- the Free Software Foundation, either version 3 of the License, or -->
<!-- (at your option) any later version. -->
<!-- -->
<!-- This program is distributed in the hope that it will be useful, -->
<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of -->
<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -->
<!-- GNU Affero General Public Licensee for more details. -->
<!-- -->
<!-- You should have received a copy of the GNU Affero General Public Licensee -->
<!-- along with this program. If not, see <https://www.gnu.org/licenses/>. -->
<template>
<ProjectWrapper
class="project-gantt"
:project-id="filters.projectId"
:view
:view-id
>
<template #header>
<template #default>
<card :has-content="false">
<div class="gantt-options">
<div class="field">
@ -61,9 +45,7 @@
</Fancycheckbox>
</div>
</card>
</template>
<template #default>
<div class="gantt-chart-container">
<card
:has-content="false"
@ -95,7 +77,7 @@ import {useI18n} from 'vue-i18n'
import type {RouteLocationNormalized} from 'vue-router'
import {useBaseStore} from '@/stores/base'
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage'
import {getFlatpickrLanguage} from '@/helpers/flatpickrLanguage'
import Foo from '@/components/misc/flatpickr/Flatpickr.vue'
import ProjectWrapper from '@/components/project/ProjectWrapper.vue'

View File

@ -6,69 +6,68 @@
>
<template #header>
<div class="filter-container">
<div class="items">
<Popup>
<template #trigger="{toggle}">
<x-button
icon="th"
variant="secondary"
@click.prevent.stop="toggle()"
>
{{ $t('project.table.columns') }}
</x-button>
</template>
<template #content="{isOpen}">
<card
class="columns-filter"
:class="{'is-open': isOpen}"
>
<Fancycheckbox v-model="activeColumns.index">
#
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.done">
{{ $t('task.attributes.done') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.title">
{{ $t('task.attributes.title') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.priority">
{{ $t('task.attributes.priority') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.labels">
{{ $t('task.attributes.labels') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.assignees">
{{ $t('task.attributes.assignees') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.dueDate">
{{ $t('task.attributes.dueDate') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.startDate">
{{ $t('task.attributes.startDate') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.endDate">
{{ $t('task.attributes.endDate') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.percentDone">
{{ $t('task.attributes.percentDone') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.doneAt">
{{ $t('task.attributes.doneAt') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.created">
{{ $t('task.attributes.created') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.updated">
{{ $t('task.attributes.updated') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.createdBy">
{{ $t('task.attributes.createdBy') }}
</Fancycheckbox>
</card>
</template>
</Popup>
<FilterPopup v-model="params" />
</div>
<Popup>
<template #trigger="{toggle}">
<x-button
icon="th"
variant="secondary"
class="mr-2"
@click.prevent.stop="toggle()"
>
{{ $t('project.table.columns') }}
</x-button>
</template>
<template #content="{isOpen}">
<card
class="columns-filter"
:class="{'is-open': isOpen}"
>
<Fancycheckbox v-model="activeColumns.index">
#
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.done">
{{ $t('task.attributes.done') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.title">
{{ $t('task.attributes.title') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.priority">
{{ $t('task.attributes.priority') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.labels">
{{ $t('task.attributes.labels') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.assignees">
{{ $t('task.attributes.assignees') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.dueDate">
{{ $t('task.attributes.dueDate') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.startDate">
{{ $t('task.attributes.startDate') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.endDate">
{{ $t('task.attributes.endDate') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.percentDone">
{{ $t('task.attributes.percentDone') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.doneAt">
{{ $t('task.attributes.doneAt') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.created">
{{ $t('task.attributes.created') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.updated">
{{ $t('task.attributes.updated') }}
</Fancycheckbox>
<Fancycheckbox v-model="activeColumns.createdBy">
{{ $t('task.attributes.createdBy') }}
</Fancycheckbox>
</card>
</template>
</Popup>
<FilterPopup v-model="params" />
</div>
</template>
@ -397,4 +396,8 @@ const taskDetailRoutes = computed(() => Object.fromEntries(
border: none;
box-shadow: none;
}
.filter-container :deep(.popup) {
top: 7rem;
}
</style>

View File

@ -2,12 +2,65 @@
import type {IProjectView} from '@/modelTypes/IProjectView'
import XButton from '@/components/input/button.vue'
import FilterInput from '@/components/project/partials/FilterInput.vue'
import {ref} from 'vue'
import {ref, watch} from 'vue'
import {transformFilterStringForApi, transformFilterStringFromApi} from '@/helpers/filters'
import {useLabelStore} from '@/stores/labels'
import {useProjectStore} from '@/stores/projects'
const {
modelValue,
} = defineProps<{
modelValue: IProjectView,
}>()
const emit = defineEmits(['update:modelValue'])
const view = ref<IProjectView>()
const labelStore = useLabelStore()
const projectStore = useProjectStore()
watch(
() => modelValue,
newValue => {
const transformed = {
...newValue,
filter: transformFilterStringFromApi(
newValue.filter,
labelId => labelStore.getLabelById(labelId)?.title,
projectId => projectStore.projects[projectId]?.title || null,
),
}
if (JSON.stringify(view.value) !== JSON.stringify(transformed)) {
view.value = transformed
}
},
{immediate: true, deep: true},
)
watch(
() => view.value,
newView => {
emit('update:modelValue', {
...newView,
filter: transformFilterStringForApi(
newView.filter,
labelTitle => labelStore.filterLabelsByQuery([], labelTitle)[0]?.id || null,
projectTitle => {
const found = projectStore.findProjectByExactname(projectTitle)
return found?.id || null
},
),
})
},
{deep: true},
)
const model = defineModel<IProjectView>()
const titleValid = ref(true)
function validateTitle() {
titleValid.value = model.value.title !== ''
titleValid.value = view.value?.title !== ''
}
</script>
@ -23,14 +76,14 @@ function validateTitle() {
<div class="control">
<input
id="title"
v-model="model.title"
v-model="view.title"
v-focus
class="input"
:placeholder="$t('project.share.links.namePlaceholder')"
@blur="validateTitle"
>
</div>
<p
<p
v-if="!titleValid"
class="help is-danger"
>
@ -49,7 +102,7 @@ function validateTitle() {
<div class="select">
<select
id="kind"
v-model="model.viewKind"
v-model="view.viewKind"
>
<option value="list">
{{ $t('project.list.title') }}
@ -69,12 +122,12 @@ function validateTitle() {
</div>
<FilterInput
v-model="model.filter"
v-model="view.filter"
:input-label="$t('project.views.filter')"
/>
<div
v-if="model.viewKind === 'kanban'"
v-if="view.viewKind === 'kanban'"
class="field"
>
<label
@ -87,7 +140,7 @@ function validateTitle() {
<div class="select">
<select
id="configMode"
v-model="model.bucketConfigurationMode"
v-model="view.bucketConfigurationMode"
>
<option value="manual">
{{ $t('project.views.bucketConfigManual') }}
@ -101,7 +154,7 @@ function validateTitle() {
</div>
<div
v-if="model.viewKind === 'kanban' && model.bucketConfigurationMode === 'filter'"
v-if="view.viewKind === 'kanban' && view.bucketConfigurationMode === 'filter'"
class="field"
>
<label class="label">
@ -109,13 +162,13 @@ function validateTitle() {
</label>
<div class="control">
<div
v-for="(b, index) in model.bucketConfiguration"
v-for="(b, index) in view.bucketConfiguration"
:key="'bucket_'+index"
class="filter-bucket"
>
<button
class="is-danger"
@click.prevent="() => model.bucketConfiguration.splice(index, 1)"
@click.prevent="() => view.bucketConfiguration.splice(index, 1)"
>
<icon icon="trash-alt" />
</button>
@ -130,7 +183,7 @@ function validateTitle() {
<div class="control">
<input
:id="'bucket_'+index+'_title'"
v-model="model.bucketConfiguration[index].title"
v-model="view.bucketConfiguration[index].title"
class="input"
:placeholder="$t('project.share.links.namePlaceholder')"
>
@ -138,7 +191,7 @@ function validateTitle() {
</div>
<FilterInput
v-model="model.bucketConfiguration[index].filter"
v-model="view.bucketConfiguration[index].filter"
:input-label="$t('project.views.filter')"
/>
</div>
@ -147,7 +200,7 @@ function validateTitle() {
<XButton
variant="secondary"
icon="plus"
@click="() => model.bucketConfiguration.push({title: '', filter: ''})"
@click="() => view.bucketConfiguration.push({title: '', filter: ''})"
>
{{ $t('project.kanban.addBucket') }}
</XButton>

View File

@ -16,7 +16,22 @@
:search-results="found"
:label="searchLabel"
@search="find"
/>
>
<template #searchResult="{option: result}">
<User
v-if="shareType === 'user'"
:avatar-size="24"
:show-username="true"
:user="result"
/>
<span
v-else
class="search-result"
>
{{ result.name }}
</span>
</template>
</Multiselect>
</p>
<p class="control">
<x-button @click="add()">
@ -173,6 +188,7 @@ import Nothing from '@/components/misc/nothing.vue'
import {success} from '@/message'
import {useAuthStore} from '@/stores/auth'
import {useConfigStore} from '@/stores/config'
import User from '@/components/misc/user.vue'
// FIXME: I think this whole thing can now only manage user/team sharing for projects? Maybe remove a little generalization?

View File

@ -21,12 +21,12 @@
@dragendBar="updateGanttTask"
@dblclickBar="openTask"
>
<template #timeunit="{value, date}">
<template #timeunit="{date}">
<div
class="timeunit-wrapper"
:class="{'today': dateIsToday(date)}"
>
<span>{{ value }}</span>
<span>{{ date.getDate() }}</span>
<span class="weekday">
{{ weekDayFromDate(date) }}
</span>

View File

@ -102,6 +102,7 @@
class="mt-2"
entity-kind="comments"
:entity-id="c.id"
:disabled="!canWrite"
/>
</div>
</div>

View File

@ -27,13 +27,13 @@ export const AUTOCOMPLETE_FIELDS = [
]
export const AVAILABLE_FILTER_FIELDS = [
'done',
'priority',
'percentDone',
...DATE_FIELDS,
...ASSIGNEE_FIELDS,
...LABEL_FIELDS,
...PROJECT_FIELDS,
'done',
'priority',
'percentDone',
]
export const FILTER_OPERATORS = [

View File

@ -383,20 +383,20 @@
"secretDocs": "Poglejte si dokumentacijo za več podrobnosti o uporabi skrite kode."
},
"views": {
"header": "Edit views",
"title": "Title",
"actions": "Actions",
"kind": "Kind",
"bucketConfigMode": "Bucket configuration mode",
"bucketConfig": "Bucket configuration",
"bucketConfigManual": "Manual",
"header": "Uredi pogled",
"title": "Naslov",
"actions": "Dejanja",
"kind": "Vrsta",
"bucketConfigMode": "Način nastavitve vedra",
"bucketConfig": "Nastavitev vedra",
"bucketConfigManual": "Ročno",
"filter": "Filter",
"create": "Create view",
"createSuccess": "The view was created successfully.",
"titleRequired": "Please provide a title.",
"delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted"
"create": "Ustvari pogled",
"createSuccess": "Pogled je bil uspešno ustvarjen.",
"titleRequired": "Prosim navedite naslov.",
"delete": "Izbriši pogled",
"deleteText": "Ali ste prepričani, da želite odstraniti ta pogled? Ne bo ga več mogoče uporabljati za ogled nalog v tem projektu. To dejanje ne bo izbrisalo nobenih opravil. Tega ni mogoče razveljaviti!",
"deleteSuccess": "Pogled je bil uspešno izbrisan"
}
},
"filters": {
@ -1065,7 +1065,7 @@
"createProject": "Ustvari projekt",
"cantArchiveIsDefault": "Tega ne morete arhivirati, ker je to vaš privzeti projekt.",
"cantDeleteIsDefault": "Tega ne morete izbrisati, ker je to vaš privzeti projekt.",
"views": "Views"
"views": "Pogledi"
},
"apiConfig": {
"url": "Vikunja URL",

View File

@ -18,7 +18,7 @@ export function getDefaultTaskFilterParams(): TaskFilterParams {
return {
sort_by: ['position', 'id'],
order_by: ['asc', 'desc'],
filter: 'done = false',
filter: '',
filter_include_nulls: false,
filter_timezone: '',
s: '',

View File

@ -1,5 +1,4 @@
@import "tooltip";
@import "labels";
@import "project";
@import "task";
@import "tasks";

View File

@ -1,77 +0,0 @@
// FIXME: should be a component <FilterContainer>
// used in
// - Kanban.vue
// - Project.vue
// - Table.vue
$filter-container-top-default: -59px;
$filter-container-top-link-share-gantt: -133px;
$filter-container-top-link-share-list: -47px;
.filter-container {
text-align: right;
width: 100%;
min-width: 400px;
max-width: 180px;
position: absolute;
right: 1.5rem;
margin-top: $filter-container-top-default;
z-index: 4;
display: flex;
justify-content: flex-end;
.button:not(:last-of-type) {
margin-right: .5rem;
}
.button {
height: $switch-view-height;
}
.card {
text-align: left;
}
@media screen and (max-width: $tablet) {
position: static;
margin: 0 0 1rem 0 !important;
max-width: 100%;
min-width: auto;
.items {
justify-content: center;
}
.search {
width: 100%;
.control:first-child {
width: 100%;
}
}
}
}
.link-share-container .gantt-chart-container .filter-container,
.gantt-chart-container .filter-container {
right: 0;
margin-top: calc(#{$filter-container-top-link-share-gantt - 2} - 7rem);
}
.link-share-container .gantt-chart-container .filter-container {
margin-top: calc(#{$filter-container-top-link-share-gantt} - 5rem);
}
.link-share-container .list-view .filter-container {
margin-top: $filter-container-top-link-share-list - 10px;
}
.link-share-container.project\.table-view,
.link-share-container.project\.list-view {
.filter-container {
right: 9rem;
margin-top: $filter-container-top-default;
}
}

View File

@ -26,7 +26,7 @@ const currentView = computed(() => {
})
function redirectToFirstViewIfNecessary() {
if (viewId === 0) {
if (viewId === 0 || !projectStore.projects[projectId]?.views.find(v => v.id === viewId)) {
// Ideally, we would do that in the router redirect, but the projects (and therefore, the views)
// are not always loaded then.
const firstViewId = projectStore.projects[projectId]?.views[0].id

View File

@ -89,7 +89,7 @@ async function saveView() {
v-model="newView"
class="mb-4"
/>
<div class="is-flex is-justify-content-end">
<div class="is-flex is-justify-content-end mb-4">
<XButton
:loading="projectViewService.loading"
@click="createView"

View File

@ -328,6 +328,7 @@
entity-kind="tasks"
:entity-id="task.id"
class="details"
:disabled="!canWrite"
/>
<!-- Attachments -->

2
go.mod
View File

@ -33,7 +33,7 @@ require (
github.com/gabriel-vasile/mimetype v1.4.3
github.com/ganigeorgiev/fexpr v0.4.0
github.com/getsentry/sentry-go v0.27.0
github.com/go-sql-driver/mysql v1.8.0
github.com/go-sql-driver/mysql v1.8.1
github.com/go-testfixtures/testfixtures/v3 v3.10.0
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
github.com/golang-jwt/jwt/v5 v5.2.1

2
go.sum
View File

@ -158,6 +158,8 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-testfixtures/testfixtures/v3 v3.10.0 h1:BrBwN7AuC+74g5qtk9D59TLGOaEa8Bw1WmIsf+SyzWc=

View File

@ -0,0 +1,51 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package migration
import (
"src.techknowlogick.com/xormigrate"
"xorm.io/xorm"
)
type projectView20240329170952 struct {
ID int64 `xorm:"autoincr not null unique pk" json:"id" param:"view"`
Filter string `xorm:"text null default null" query:"filter" json:"filter"`
ViewKind int `xorm:"not null" json:"view_kind"`
}
func (projectView20240329170952) TableName() string {
return "project_views"
}
func init() {
migrations = append(migrations, &xormigrate.Migration{
ID: "20240329170952",
Description: "Update default filter for list views to hide completed tasks",
Migrate: func(tx *xorm.Engine) error {
// Update the filter for all list views to hide completed tasks unless the filter is already set
_, err := tx.Where("view_kind = ? AND filter = ?", 0, "").Cols("filter").Update(&projectView20240329170952{Filter: "done = false"})
if err != nil {
return err
}
return nil
},
Rollback: func(tx *xorm.Engine) error {
return nil
},
})
}

View File

@ -384,27 +384,27 @@ func (b *Bucket) Delete(s *xorm.Session, a web.Auth) (err error) {
}
// Get the default bucket
p, err := GetProjectViewByIDAndProject(s, b.ProjectViewID, b.ProjectID)
pv, err := GetProjectViewByIDAndProject(s, b.ProjectViewID, b.ProjectID)
if err != nil {
return
}
var updateProject bool
if b.ID == p.DefaultBucketID {
p.DefaultBucketID = 0
updateProject = true
var updateProjectView bool
if b.ID == pv.DefaultBucketID {
pv.DefaultBucketID = 0
updateProjectView = true
}
if b.ID == p.DoneBucketID {
p.DoneBucketID = 0
updateProject = true
if b.ID == pv.DoneBucketID {
pv.DoneBucketID = 0
updateProjectView = true
}
if updateProject {
err = p.Update(s, a)
if updateProjectView {
err = pv.Update(s, a)
if err != nil {
return
}
}
defaultBucketID, err := getDefaultBucketID(s, p)
defaultBucketID, err := getDefaultBucketID(s, pv)
if err != nil {
return err
}

View File

@ -216,10 +216,10 @@ func TestBucket_Delete(t *testing.T) {
err := b.Delete(s, u)
require.NoError(t, err)
db.AssertMissing(t, "project_views", map[string]interface{}{
db.AssertExists(t, "project_views", map[string]interface{}{
"id": b.ProjectViewID,
"done_bucket_id": 0,
})
}, false)
})
}

View File

@ -991,7 +991,12 @@ func (p *Project) Create(s *xorm.Session, a web.Auth) (err error) {
return
}
return p.ReadOne(s, a)
fullProject, err := GetProjectSimpleByID(s, p.ID)
if err != nil {
return
}
return fullProject.ReadOne(s, a)
}
func (p *Project) isDefaultProject(s *xorm.Session) (is bool, err error) {

View File

@ -331,11 +331,19 @@ func (p *ProjectView) Update(s *xorm.Session, _ web.Auth) (err error) {
return
}
_, err = s.ID(p.ID).Update(p)
if err != nil {
return
}
_, err = s.
ID(p.ID).
Cols(
"title",
"view_kind",
"filter",
"position",
"bucket_configuration_mode",
"bucket_configuration",
"default_bucket_id",
"done_bucket_id",
).
Update(p)
return
}
@ -383,6 +391,7 @@ func CreateDefaultViewsForProject(s *xorm.Session, project *Project, a web.Auth,
Title: "List",
ViewKind: ProjectViewKindList,
Position: 100,
Filter: "done = false",
}
err = createProjectView(s, list, a, createBacklogBucket)
if err != nil {