Compare commits

..

26 Commits

Author SHA1 Message Date
c233c99ab7 fix(deps): update dependency @intlify/unplugin-vue-i18n to v4
All checks were successful
continuous-integration/drone/pr Build is passing
2024-04-05 21:09:30 +00:00
90055d063c chore(deps): update dev-dependencies (#2229)
All checks were successful
continuous-integration/drone/push Build is passing
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
f0d695e789 fix(views): remove default filter from frontend, apply by default to new list views instead (#2240)
All checks were successful
continuous-integration/drone/push Build is passing
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
95276ceebe
fix(reactions): do not enable reaction picker when the current user does not have write access
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-02 14:48:13 +02:00
1558921f42
fix(test): use correct selector in Cypress test
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-02 14:31:15 +02:00
bf5088e546
fix(sharing): show user display name and avatar when displaying search results
Some checks failed
continuous-integration/drone/push Build is failing
Resolves https://community.vikunja.io/t/autogenerated-username-using-google-openid/2183/12
2024-04-02 14:29:22 +02:00
6f366d4907
feat(views): lint
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-02 14:04:17 +02:00
d7554d9e70
feat(views): hide view switcher when there is only one view 2024-04-02 14:02:59 +02:00
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
13cab62d14
fix(views): transform view filter before and after loading it from the api
Some checks failed
continuous-integration/drone/push Build is failing
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
81de986d8d
fix(gantt): correctly show day in chart 2024-04-02 12:53:14 +02:00
915f677c2a
fix(views): correctly pass view id to wrapper when gantt view is active 2024-04-02 12:50:10 +02:00
8a6e3d5bd7
fix(views): use correct assertion in test
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-02 12:42:07 +02:00
81fe8391e4
fix(project): load full project after creating a project
Some checks failed
continuous-integration/drone/push Build is failing
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
89e37b88d9
fix(views): update all fields when updating a view
Some checks failed
continuous-integration/drone/push Build is failing
Resolves #2241
2024-03-29 18:19:16 +01:00
cc6801c5b1
fix(filter): make sure highlight works for doneAt attribute
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 18:09:02 +01:00
767b058915
fix(filter): add white background to filter input 2024-03-29 18:07:37 +01:00
2c0d3f2885
fix(views): add bottom spacing 2024-03-29 18:05:30 +01:00
fa170b9397 fix(deps): update sentry-javascript monorepo to v7.109.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-03-28 22:06:03 +00:00
a5fd6f834a
chore(deps): sign drone config
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-28 23:00:24 +01:00
8984e0e9f0 fix(deps): update module github.com/go-sql-driver/mysql to v1.8.1
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2024-03-28 21:06:31 +00:00
176c41dc40 fix(deps): update dependency express to v4.19.2
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-28 20:57:07 +00:00
c4d3d99cd4 fix: pick first available view if currently configured view got deleted (#2235)
Some checks failed
continuous-integration/drone/push Build is failing
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
30b4ed6b23 fix(deps): update dependency dompurify to v3.0.11
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-28 20:53:00 +00:00
aed92d1cd2 fix(deps): update sentry-javascript monorepo to v7.108.0
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-28 20:49:17 +00:00
Frederick [Bot]
2239d73797 chore(i18n): update translations via Crowdin
All checks were successful
continuous-integration/drone/push Build is passing
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

@ -86,6 +86,7 @@ const showIconOnly = computed(() => icon !== '' && typeof slots.default === 'und
box-shadow: var(--shadow-sm);
display: inline-flex;
white-space: var(--button-white-space);
line-height: 1;
&:hover {
box-shadow: var(--shadow-md);

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 {
@media screen and (max-width: $tablet) {
min-height: $switch-view-height;
margin-bottom: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
@media screen and (max-width: $tablet) {
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"

View File

@ -6,12 +6,12 @@
>
<template #header>
<div class="filter-container">
<div class="items">
<Popup>
<template #trigger="{toggle}">
<x-button
icon="th"
variant="secondary"
class="mr-2"
@click.prevent.stop="toggle()"
>
{{ $t('project.table.columns') }}
@ -69,7 +69,6 @@
</Popup>
<FilterPopup v-model="params" />
</div>
</div>
</template>
<template #default>
@ -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,7 +76,7 @@ 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')"
@ -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 {