Merge branch 'main' of https://kolaente.dev/vikunja/vikunja into fix/migration-doc
This commit is contained in:
commit
1524af918b
|
@ -62,6 +62,9 @@ service:
|
|||
allowiconchanges: true
|
||||
# Allow using a custom logo via external URL.
|
||||
customlogourl: ''
|
||||
# Enables the public team feature. If enabled, it is possible to configure teams to be public, which makes them
|
||||
# discoverable when sharing a project, therefore not only showing teams the user is member of.
|
||||
enablepublicteams: false
|
||||
|
||||
sentry:
|
||||
# If set to true, enables anonymous error tracking of api errors via Sentry. This allows us to gather more
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "29.1.1",
|
||||
"electron": "29.1.4",
|
||||
"electron-builder": "24.13.3"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
@ -769,10 +769,10 @@ electron-publish@24.13.1:
|
|||
lazy-val "^1.0.5"
|
||||
mime "^2.5.2"
|
||||
|
||||
electron@29.1.1:
|
||||
version "29.1.1"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-29.1.1.tgz#e9cb11311324e4b43a3e73667cd2b65a30e8fa34"
|
||||
integrity sha512-cXN15NgCi7MkzGo5/23ZQbii+0UfhmUiDjACunmzcUofYCjF42XhFbL7JZnwgI0qtBCCeJU8qZNZt9lU91gUFw==
|
||||
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==
|
||||
dependencies:
|
||||
"@electron/get" "^2.0.0"
|
||||
"@types/node" "^20.9.0"
|
||||
|
|
|
@ -346,6 +346,17 @@ Full path: `service.customlogourl`
|
|||
Environment path: `VIKUNJA_SERVICE_CUSTOMLOGOURL`
|
||||
|
||||
|
||||
### enablepublicteams
|
||||
|
||||
discoverable when sharing a project, therefore not only showing teams the user is member of.
|
||||
|
||||
Default: `false`
|
||||
|
||||
Full path: `service.enablepublicteams`
|
||||
|
||||
Environment path: `VIKUNJA_SERVICE_ENABLEPUBLICTEAMS`
|
||||
|
||||
|
||||
---
|
||||
|
||||
## sentry
|
||||
|
|
|
@ -34,6 +34,22 @@ Claims in turn are assertions containing information about the token bearer, usu
|
|||
**Scopes** are requested by the client when redirecting the end-user to the Authorization Server for authentication, and indirectly control which claims are included in the resulting tokens.
|
||||
There's certain default scopes, but its also possible to define custom scopes, which are used by the feature assigning users to Teams automatically.
|
||||
|
||||
## Supported and required claims
|
||||
|
||||
Vikunja only requires a few claims to be present in the ID token to successfully authenticate the user.
|
||||
Additional claims can be added though to customize behaviour during user creation.
|
||||
|
||||
The following table gives an overview about the claims supported by Vikunja. The scope column lists the scope that should request the claim according to the [OpenID Connect Standard](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims). It omits the claims such as `sub` or `issuer` required by the `openid` scope, which must always be present.
|
||||
|
||||
| Claim | Type | Scope | Comment |
|
||||
| ------|------|-------|---------|
|
||||
| email | required | email | Sets the email address of the user. Taken from the `userinfo` endpoint if not present in ID token. User creation fails if claim not present and userinfo lookup fails. |
|
||||
| name | optional | profile | Sets the display name of the user. Taken from the `userinfo` endpoint if not present in ID token. |
|
||||
| preferred_username | optional | profile | Sets the username of the user. Taken from the `userinfo` endpoint if not present in ID token. If this also doesn't contain the claim, use the `nickname` claim from `userinfo` instead. If that one is not available either, the username is auto-generated by Vikunja. |
|
||||
| vikunja_groups | optional | N/A | Can be used to automatically assign users to teams. See below for a more detailed explanation about the expected format and implementation examples. |
|
||||
|
||||
If one of the claims `email`, `name` or `preferred_username` is missing from the ID token, Vikunja will attempt to query the `userinfo` endpoint to obtain the information from there.
|
||||
|
||||
## Configuring OIDC Authentication
|
||||
|
||||
To achieve authentication via an external provider, it is required to (a) configure a confidential Client on your OAuth 2.0 provider and (b) configure Vikunja to authenticate against this provider.
|
||||
|
@ -99,7 +115,7 @@ It depends on the provider being used as well as the preferences of the administ
|
|||
Typically you'd want to request an additional scope (e.g. `vikunja_scope`) which then triggers the identity provider to add the claim.
|
||||
If the `vikunja_groups` is part of the **ID token**, Vikunja will start the procedure and import teams and team memberships.
|
||||
|
||||
The claim structure expexted by Vikunja is as follows:
|
||||
The minimal claim structure expected by Vikunja is as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -116,6 +132,21 @@ The claim structure expexted by Vikunja is as follows:
|
|||
}
|
||||
```
|
||||
|
||||
It is also possible to pass the `description` and the `isPublic` flag as optional parameters. If not present, the description will be empty and project visibility defaults to false.
|
||||
|
||||
```json
|
||||
{
|
||||
"vikunja_groups": [
|
||||
{
|
||||
"name": "team 3",
|
||||
"oidcID": 33349,
|
||||
"description": "My Team Description",
|
||||
"isPublic": true
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
For each team, you need to define a team `name` and an `oidcID`, where the `oidcID` can be any string with a length of less than 250 characters.
|
||||
The `oidcID` is used to uniquely identify the team, so please make sure to keep this unique.
|
||||
|
||||
|
|
|
@ -97,6 +97,7 @@ This document describes the different errors Vikunja can return.
|
|||
| 4022 | 400 | The task has a relative reminder which does not specify relative to what. |
|
||||
| 4023 | 409 | Tried to create a task relation which would create a cycle. |
|
||||
| 4024 | 400 | The provided filter expression is invalid. |
|
||||
| 4025 | 400 | The reaction kind is invalid. |
|
||||
|
||||
## Team
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ The available operators for filtering include:
|
|||
* `<`: Less than
|
||||
* `<=`: Less than or equal to
|
||||
* `like`: Matches a pattern (using wildcard `%`)
|
||||
* `in`: Matches any value in a list
|
||||
* `in`: Matches any value in a comma-seperated list of values
|
||||
|
||||
To combine multiple conditions, you can use the following logical operators:
|
||||
|
||||
|
|
|
@ -58,8 +58,8 @@
|
|||
"@infectoone/vue-ganttastic": "2.2.0",
|
||||
"@intlify/unplugin-vue-i18n": "3.0.1",
|
||||
"@kyvg/vue3-notification": "3.2.0",
|
||||
"@sentry/tracing": "7.106.0",
|
||||
"@sentry/vue": "7.106.0",
|
||||
"@sentry/tracing": "7.107.0",
|
||||
"@sentry/vue": "7.107.0",
|
||||
"@tiptap/core": "2.2.4",
|
||||
"@tiptap/extension-blockquote": "2.2.4",
|
||||
"@tiptap/extension-bold": "2.2.4",
|
||||
|
@ -97,11 +97,11 @@
|
|||
"@types/lodash.clonedeep": "4.5.9",
|
||||
"@vueuse/core": "10.9.0",
|
||||
"@vueuse/router": "10.9.0",
|
||||
"axios": "1.6.7",
|
||||
"axios": "1.6.8",
|
||||
"blurhash": "2.0.5",
|
||||
"bulma-css-variables": "0.9.33",
|
||||
"camel-case": "4.1.2",
|
||||
"date-fns": "3.3.1",
|
||||
"date-fns": "3.5.0",
|
||||
"dayjs": "1.11.10",
|
||||
"dompurify": "3.0.9",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
|
@ -117,12 +117,13 @@
|
|||
"snake-case": "3.0.4",
|
||||
"sortablejs": "1.15.2",
|
||||
"tippy.js": "6.3.7",
|
||||
"ufo": "1.4.0",
|
||||
"ufo": "1.5.1",
|
||||
"vue": "3.4.21",
|
||||
"vue-advanced-cropper": "2.8.8",
|
||||
"vue-flatpickr-component": "11.0.5",
|
||||
"vue-i18n": "9.10.1",
|
||||
"vue-i18n": "9.10.2",
|
||||
"vue-router": "4.3.0",
|
||||
"vuemoji-picker": "0.2.1",
|
||||
"workbox-precaching": "7.0.0",
|
||||
"zhyswan-vuedraggable": "4.1.3"
|
||||
},
|
||||
|
@ -131,8 +132,8 @@
|
|||
"@cypress/vite-dev-server": "5.0.7",
|
||||
"@cypress/vue": "6.0.0",
|
||||
"@faker-js/faker": "8.4.1",
|
||||
"@histoire/plugin-screenshot": "0.17.8",
|
||||
"@histoire/plugin-vue": "0.17.12",
|
||||
"@histoire/plugin-screenshot": "0.17.14",
|
||||
"@histoire/plugin-vue": "0.17.14",
|
||||
"@rushstack/eslint-patch": "1.7.2",
|
||||
"@tsconfig/node18": "18.2.2",
|
||||
"@types/codemirror": "5.60.15",
|
||||
|
@ -141,50 +142,51 @@
|
|||
"@types/is-touch-device": "1.0.2",
|
||||
"@types/lodash.debounce": "4.0.9",
|
||||
"@types/marked": "5.0.2",
|
||||
"@types/node": "20.11.25",
|
||||
"@types/node": "20.11.28",
|
||||
"@types/postcss-preset-env": "7.7.0",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "7.1.1",
|
||||
"@typescript-eslint/parser": "7.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "7.2.0",
|
||||
"@typescript-eslint/parser": "7.2.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.4",
|
||||
"@vue/test-utils": "2.4.5",
|
||||
"@vue/tsconfig": "0.5.1",
|
||||
"autoprefixer": "10.4.18",
|
||||
"browserslist": "4.23.0",
|
||||
"caniuse-lite": "1.0.30001596",
|
||||
"caniuse-lite": "1.0.30001597",
|
||||
"css-has-pseudo": "6.0.2",
|
||||
"csstype": "3.1.3",
|
||||
"cypress": "13.6.6",
|
||||
"esbuild": "0.20.1",
|
||||
"cypress": "13.7.0",
|
||||
"esbuild": "0.20.2",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-vue": "9.22.0",
|
||||
"happy-dom": "13.7.1",
|
||||
"histoire": "0.17.9",
|
||||
"eslint-plugin-vue": "9.23.0",
|
||||
"happy-dom": "13.8.6",
|
||||
"histoire": "0.17.14",
|
||||
"postcss": "8.4.35",
|
||||
"postcss-easing-gradients": "3.0.1",
|
||||
"postcss-easings": "4.0.0",
|
||||
"postcss-focus-within": "8.0.1",
|
||||
"postcss-preset-env": "9.5.0",
|
||||
"rollup": "4.12.1",
|
||||
"postcss-preset-env": "9.5.1",
|
||||
"rollup": "4.13.0",
|
||||
"rollup-plugin-visualizer": "5.12.0",
|
||||
"sass": "1.71.1",
|
||||
"sass": "1.72.0",
|
||||
"start-server-and-test": "2.0.3",
|
||||
"typescript": "5.4.2",
|
||||
"vite": "5.1.5",
|
||||
"vite": "5.1.6",
|
||||
"vite-plugin-inject-preload": "1.3.3",
|
||||
"vite-plugin-pwa": "0.19.2",
|
||||
"vite-plugin-pwa": "0.19.4",
|
||||
"vite-plugin-sentry": "1.4.0",
|
||||
"vite-svg-loader": "5.1.0",
|
||||
"vitest": "1.3.1",
|
||||
"vitest": "1.4.0",
|
||||
"vue-tsc": "2.0.6",
|
||||
"wait-on": "7.2.0",
|
||||
"workbox-cli": "7.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"flexsearch@0.7.31": "patches/flexsearch@0.7.31.patch"
|
||||
"flexsearch@0.7.31": "patches/flexsearch@0.7.31.patch",
|
||||
"@github/hotkey@3.1.0": "patches/@github__hotkey@3.1.0.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
28
frontend/patches/@github__hotkey@3.1.0.patch
Normal file
28
frontend/patches/@github__hotkey@3.1.0.patch
Normal file
|
@ -0,0 +1,28 @@
|
|||
diff --git a/dist/index.js b/dist/index.js
|
||||
index b6e6e0a6864cb00bc085b8d4503a705cb3bc8404..0466ef46406b0df41c8d0bb9a5bac9eabf4a50de 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -368,10 +368,12 @@ const sequenceTracker = new SequenceTracker({
|
||||
function keyDownHandler(event) {
|
||||
if (event.defaultPrevented)
|
||||
return;
|
||||
- if (!(event.target instanceof Node))
|
||||
+ const target = event.explicitOriginalTarget || event.target;
|
||||
+ if (target.shadowRoot)
|
||||
return;
|
||||
- if (isFormField(event.target)) {
|
||||
- const target = event.target;
|
||||
+ if (!(target instanceof Node))
|
||||
+ return;
|
||||
+ if (isFormField(target)) {
|
||||
if (!target.id)
|
||||
return;
|
||||
if (!target.ownerDocument.querySelector(`[data-hotkey-scope="${target.id}"]`))
|
||||
@@ -385,7 +387,6 @@ function keyDownHandler(event) {
|
||||
sequenceTracker.registerKeypress(event);
|
||||
currentTriePosition = newTriePosition;
|
||||
if (newTriePosition instanceof Leaf) {
|
||||
- const target = event.target;
|
||||
let shouldFire = false;
|
||||
let elementToFire;
|
||||
const formField = isFormField(target);
|
File diff suppressed because it is too large
Load Diff
1
frontend/public/emojis.json
Normal file
1
frontend/public/emojis.json
Normal file
File diff suppressed because one or more lines are too long
193
frontend/src/components/input/Reactions.vue
Normal file
193
frontend/src/components/input/Reactions.vue
Normal file
|
@ -0,0 +1,193 @@
|
|||
<script setup lang="ts">
|
||||
import type {IReactionPerEntity, ReactionKind} from '@/modelTypes/IReaction'
|
||||
import {VuemojiPicker} from 'vuemoji-picker'
|
||||
import ReactionService from '@/services/reactions'
|
||||
import ReactionModel from '@/models/reaction'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import type {IUser} from '@/modelTypes/IUser'
|
||||
import {getDisplayName} from '@/models/user'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {nextTick, onBeforeUnmount, onMounted, ref} from 'vue'
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useColorScheme} from '@/composables/useColorScheme'
|
||||
|
||||
const {
|
||||
entityKind,
|
||||
entityId,
|
||||
} = defineProps<{
|
||||
entityKind: ReactionKind,
|
||||
entityId: number,
|
||||
}>()
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const {t} = useI18n()
|
||||
const reactionService = new ReactionService()
|
||||
const {isDark} = useColorScheme()
|
||||
|
||||
const model = defineModel<IReactionPerEntity>()
|
||||
|
||||
async function addReaction(value: string) {
|
||||
const reaction = new ReactionModel({
|
||||
id: entityId,
|
||||
kind: entityKind,
|
||||
value,
|
||||
})
|
||||
await reactionService.create(reaction)
|
||||
showEmojiPicker.value = false
|
||||
|
||||
if (typeof model.value === 'undefined') {
|
||||
model.value = {}
|
||||
}
|
||||
|
||||
if (typeof model.value[reaction.value] === 'undefined') {
|
||||
model.value[reaction.value] = [authStore.info]
|
||||
} else {
|
||||
model.value[reaction.value].push(authStore.info)
|
||||
}
|
||||
}
|
||||
|
||||
async function removeReaction(value: string) {
|
||||
const reaction = new ReactionModel({
|
||||
id: entityId,
|
||||
kind: entityKind,
|
||||
value,
|
||||
})
|
||||
await reactionService.delete(reaction)
|
||||
showEmojiPicker.value = false
|
||||
|
||||
const userIndex = model.value[reaction.value].findIndex(u => u.id === authStore.info?.id)
|
||||
if (userIndex !== -1) {
|
||||
model.value[reaction.value].splice(userIndex, 1)
|
||||
}
|
||||
if(model.value[reaction.value].length === 0) {
|
||||
delete model.value[reaction.value]
|
||||
}
|
||||
}
|
||||
|
||||
function getReactionTooltip(users: IUser[], value: string) {
|
||||
const names = users.map(u => getDisplayName(u))
|
||||
|
||||
if (names.length === 1) {
|
||||
return t('reaction.reactedWith', {user: names[0], value})
|
||||
}
|
||||
|
||||
if (names.length > 1 && names.length < 10) {
|
||||
return t('reaction.reactedWithAnd', {
|
||||
users: names.slice(0, names.length - 1).join(', '),
|
||||
lastUser: names[names.length - 1],
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
return t('reaction.reactedWithAndMany', {
|
||||
users: names.slice(0, 10).join(', '),
|
||||
num: names.length - 10,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
const showEmojiPicker = ref(false)
|
||||
const emojiPickerRef = ref<HTMLElement | null>(null)
|
||||
|
||||
function hideEmojiPicker(e: MouseEvent) {
|
||||
if (showEmojiPicker.value) {
|
||||
closeWhenClickedOutside(e, emojiPickerRef.value.$el, () => showEmojiPicker.value = false)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => document.addEventListener('click', hideEmojiPicker))
|
||||
onBeforeUnmount(() => document.removeEventListener('click', hideEmojiPicker))
|
||||
|
||||
const emojiPickerButtonRef = ref<HTMLElement | null>(null)
|
||||
const reactionContainerRef = ref<HTMLElement | null>(null)
|
||||
const emojiPickerPosition = ref()
|
||||
|
||||
function toggleEmojiPicker() {
|
||||
if (!showEmojiPicker.value) {
|
||||
const rect = emojiPickerButtonRef.value?.$el.getBoundingClientRect()
|
||||
const container = reactionContainerRef.value?.getBoundingClientRect()
|
||||
const left = rect.left - container.left + rect.width
|
||||
|
||||
emojiPickerPosition.value = {
|
||||
left: left === 0 ? undefined : left,
|
||||
}
|
||||
}
|
||||
|
||||
nextTick(() => showEmojiPicker.value = !showEmojiPicker.value)
|
||||
}
|
||||
|
||||
function hasCurrentUserReactedWithEmoji(value: string): boolean {
|
||||
const user = model.value[value].find(u => u.id === authStore.info.id)
|
||||
return typeof user !== 'undefined'
|
||||
}
|
||||
|
||||
async function toggleReaction(value: string) {
|
||||
if (hasCurrentUserReactedWithEmoji(value)) {
|
||||
return removeReaction(value)
|
||||
}
|
||||
|
||||
return addReaction(value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="reactionContainerRef"
|
||||
class="reactions"
|
||||
>
|
||||
<BaseButton
|
||||
v-for="(users, value) in (model as IReactionPerEntity)"
|
||||
:key="'button' + value"
|
||||
v-tooltip="getReactionTooltip(users, value)"
|
||||
class="reaction-button"
|
||||
:class="{'current-user-has-reacted': hasCurrentUserReactedWithEmoji(value)}"
|
||||
@click="toggleReaction(value)"
|
||||
>
|
||||
{{ value }} {{ users.length }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
ref="emojiPickerButtonRef"
|
||||
v-tooltip="$t('reaction.add')"
|
||||
class="reaction-button"
|
||||
@click.stop="toggleEmojiPicker"
|
||||
>
|
||||
<icon :icon="['far', 'face-laugh']" />
|
||||
</BaseButton>
|
||||
<CustomTransition name="fade">
|
||||
<VuemojiPicker
|
||||
v-if="showEmojiPicker"
|
||||
ref="emojiPickerRef"
|
||||
class="emoji-picker"
|
||||
:style="{left: emojiPickerPosition?.left + 'px'}"
|
||||
data-source="/emojis.json"
|
||||
:is-dark="isDark"
|
||||
@emojiClick="detail => addReaction(detail.unicode)"
|
||||
/>
|
||||
</CustomTransition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.reaction-button {
|
||||
margin-right: .25rem;
|
||||
margin-bottom: .25rem;
|
||||
padding: .175rem .5rem .15rem;
|
||||
border: 1px solid var(--grey-400);
|
||||
background: var(--grey-100);
|
||||
border-radius: 100px;
|
||||
font-size: .75rem;
|
||||
|
||||
&.current-user-has-reacted {
|
||||
border-color: var(--primary);
|
||||
background-color: hsla(var(--primary-h), var(--primary-s), var(--primary-light-l), 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-picker {
|
||||
position: absolute;
|
||||
z-index: 99;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
</style>
|
|
@ -63,6 +63,7 @@
|
|||
|
||||
<div class="flatpickr-container">
|
||||
<flat-pickr
|
||||
ref="flatPickrRef"
|
||||
v-model="flatPickrDate"
|
||||
:config="flatPickerConfig"
|
||||
/>
|
||||
|
@ -70,7 +71,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, toRef, watch, computed, type PropType} from 'vue'
|
||||
import {computed, onBeforeUnmount, onMounted, type PropType, ref, toRef, watch} from 'vue'
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
import 'flatpickr/dist/flatpickr.css'
|
||||
|
||||
|
@ -105,6 +106,7 @@ watch(
|
|||
{immediate: true},
|
||||
)
|
||||
|
||||
const flatPickrRef = ref<HTMLElement | null>(null)
|
||||
const flatPickerConfig = computed(() => ({
|
||||
altFormat: t('date.altFormatLong'),
|
||||
altInput: true,
|
||||
|
@ -142,6 +144,41 @@ const flatPickrDate = computed({
|
|||
},
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
const inputs = flatPickrRef.value?.$el.parentNode.querySelectorAll('.numInputWrapper > input.numInput')
|
||||
inputs.forEach(i => {
|
||||
i.addEventListener('input', handleFlatpickrInput)
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const inputs = flatPickrRef.value?.$el.parentNode.querySelectorAll('.numInputWrapper > input.numInput')
|
||||
inputs.forEach(i => {
|
||||
i.removeEventListener('input', handleFlatpickrInput)
|
||||
})
|
||||
})
|
||||
|
||||
// Flatpickr only returns a change event when the value in the input it's referring to changes.
|
||||
// That means it will usually only trigger when the focus is moved out of the input field.
|
||||
// This is fine most of the time. However, since we're displaying flatpickr in a popup,
|
||||
// the whole html dom instance might get destroyed, before the change event had a
|
||||
// chance to fire. In that case, it would not update the date value. To fix
|
||||
// this, we're now listening on every change and bubble them up as soon
|
||||
// as they happen.
|
||||
function handleFlatpickrInput(e) {
|
||||
const newDate = new Date(date?.value || 'now')
|
||||
if (e.target.classList.contains('flatpickr-minute')) {
|
||||
newDate.setMinutes(e.target.value)
|
||||
}
|
||||
if (e.target.classList.contains('flatpickr-hour')) {
|
||||
newDate.setHours(e.target.value)
|
||||
}
|
||||
if (e.target.classList.contains('cur-year')) {
|
||||
newDate.setFullYear(e.target.value)
|
||||
}
|
||||
flatPickrDate.value = newDate
|
||||
}
|
||||
|
||||
|
||||
function setDateValue(dateString: string | Date | null) {
|
||||
if (dateString === null) {
|
||||
|
|
|
@ -139,7 +139,7 @@
|
|||
<BaseButton
|
||||
v-tooltip="$t('input.editor.image')"
|
||||
class="editor-toolbar__button"
|
||||
@click="openImagePicker"
|
||||
@click="e => emit('imageUploadClicked', e)"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon icon="fa-image" />
|
||||
|
@ -347,16 +347,14 @@ const {
|
|||
editor: Editor,
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['imageUploadClicked'])
|
||||
|
||||
const tableMode = ref(false)
|
||||
|
||||
function toggleTableMode() {
|
||||
tableMode.value = !tableMode.value
|
||||
}
|
||||
|
||||
function openImagePicker() {
|
||||
document.getElementById('tiptap__image-upload').click()
|
||||
}
|
||||
|
||||
function setLink(event) {
|
||||
setLinkInEditor(event.target.getBoundingClientRect(), editor)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<EditorToolbar
|
||||
v-if="editor && isEditing"
|
||||
:editor="editor"
|
||||
:upload-callback="uploadCallback"
|
||||
@imageUploadClicked="triggerImageInput"
|
||||
/>
|
||||
<BubbleMenu
|
||||
v-if="editor && isEditing"
|
||||
|
@ -374,7 +374,7 @@ const editor = useEditor({
|
|||
Typography,
|
||||
Underline,
|
||||
Link.configure({
|
||||
openOnClick: true,
|
||||
openOnClick: false,
|
||||
validate: (href: string) => /^https?:\/\//.test(href),
|
||||
}),
|
||||
Table.configure({
|
||||
|
@ -489,6 +489,15 @@ function uploadAndInsertFiles(files: File[] | FileList) {
|
|||
})
|
||||
}
|
||||
|
||||
function triggerImageInput(event) {
|
||||
if (typeof uploadCallback !== 'undefined') {
|
||||
uploadInputRef.value?.click()
|
||||
return
|
||||
}
|
||||
|
||||
addImage(event)
|
||||
}
|
||||
|
||||
async function addImage(event) {
|
||||
|
||||
if (typeof uploadCallback !== 'undefined') {
|
||||
|
@ -522,16 +531,20 @@ onMounted(async () => {
|
|||
|
||||
await nextTick()
|
||||
|
||||
if (typeof uploadCallback !== 'undefined') {
|
||||
const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
|
||||
input?.addEventListener('paste', handleImagePaste)
|
||||
}
|
||||
|
||||
setModeAndValue(modelValue)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
nextTick(() => {
|
||||
if (typeof uploadCallback !== 'undefined') {
|
||||
const input = tiptapInstanceRef.value?.querySelectorAll('.tiptap__editor')[0]?.children[0]
|
||||
input?.removeEventListener('paste', handleImagePaste)
|
||||
}
|
||||
})
|
||||
if (editShortcut !== '') {
|
||||
document.removeEventListener('keydown', setFocusToEditor)
|
||||
|
@ -558,6 +571,10 @@ function handleImagePaste(event) {
|
|||
|
||||
// See https://github.com/github/hotkey/discussions/85#discussioncomment-5214660
|
||||
function setFocusToEditor(event) {
|
||||
if (event.target.shadowRoot) {
|
||||
return
|
||||
}
|
||||
|
||||
const hotkeyString = eventToHotkeyString(event)
|
||||
if (!hotkeyString) return
|
||||
if (hotkeyString !== editShortcut ||
|
||||
|
|
|
@ -87,7 +87,7 @@ import {
|
|||
faStar,
|
||||
faSun,
|
||||
faTimesCircle,
|
||||
faCircleQuestion,
|
||||
faCircleQuestion, faFaceLaugh,
|
||||
} from '@fortawesome/free-regular-svg-icons'
|
||||
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
|
||||
|
||||
|
@ -186,6 +186,7 @@ library.add(faXmarksLines)
|
|||
library.add(faFont)
|
||||
library.add(faRulerHorizontal)
|
||||
library.add(faUnderline)
|
||||
library.add(faFaceLaugh)
|
||||
|
||||
// overwriting the wrong types
|
||||
export default FontAwesomeIcon as unknown as FontAwesomeIconFixedTypes
|
|
@ -10,14 +10,17 @@ import User from '@/components/misc/user.vue'
|
|||
import ProjectUserService from '@/services/projectUsers'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import {
|
||||
DATE_FIELDS,
|
||||
ASSIGNEE_FIELDS,
|
||||
AUTOCOMPLETE_FIELDS,
|
||||
AVAILABLE_FILTER_FIELDS,
|
||||
DATE_FIELDS,
|
||||
FILTER_JOIN_OPERATOR,
|
||||
FILTER_OPERATORS,
|
||||
FILTER_OPERATORS_REGEX, LABEL_FIELDS,
|
||||
FILTER_OPERATORS_REGEX,
|
||||
getFilterFieldRegexPattern,
|
||||
LABEL_FIELDS,
|
||||
} from '@/helpers/filters'
|
||||
import {useDebounceFn} from '@vueuse/core'
|
||||
|
||||
const {
|
||||
modelValue,
|
||||
|
@ -104,15 +107,26 @@ const highlightedFilterQuery = computed(() => {
|
|||
})
|
||||
LABEL_FIELDS
|
||||
.forEach(f => {
|
||||
const pattern = new RegExp(f + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*([\'"]?)([^\'"\\s]+\\1?)?', 'ig')
|
||||
highlighted = highlighted.replaceAll(pattern, (match, token, start, value) => {
|
||||
const pattern = getFilterFieldRegexPattern(f)
|
||||
highlighted = highlighted.replaceAll(pattern, (match, prefix, operator, space, value) => {
|
||||
|
||||
if (typeof value === 'undefined') {
|
||||
value = ''
|
||||
}
|
||||
|
||||
const label = labelStore.getLabelsByExactTitles([value])[0] || undefined
|
||||
let labelTitles = [value.trim()]
|
||||
if (operator === 'in' || operator === '?=') {
|
||||
labelTitles = value.split(',').map(v => v.trim())
|
||||
}
|
||||
|
||||
return `${f} ${token} <span class="filter-query__label_value" style="background-color: ${label?.hexColor}; color: ${label?.textColor}">${label?.title ?? value}<span>`
|
||||
const labelsHtml: string[] = []
|
||||
labelTitles.forEach(t => {
|
||||
const label = labelStore.getLabelByExactTitle(t) || undefined
|
||||
labelsHtml.push(`<span class="filter-query__label_value" style="background-color: ${label?.hexColor}; color: ${label?.textColor}">${label?.title ?? t}</span>`)
|
||||
})
|
||||
|
||||
const endSpace = value.endsWith(' ') ? ' ' : ''
|
||||
return `${f} ${operator} ${labelsHtml.join(', ')}${endSpace}`
|
||||
})
|
||||
})
|
||||
FILTER_OPERATORS
|
||||
|
@ -175,6 +189,7 @@ const projectStore = useProjectStore()
|
|||
function handleFieldInput() {
|
||||
const cursorPosition = filterInput.value.selectionStart
|
||||
const textUpToCursor = filterQuery.value.substring(0, cursorPosition)
|
||||
autocompleteResults.value = []
|
||||
|
||||
AUTOCOMPLETE_FIELDS.forEach(field => {
|
||||
const pattern = new RegExp('(' + field + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*)([\'"]?)([^\'"&|()]+\\1?)?$', 'ig')
|
||||
|
@ -184,26 +199,31 @@ function handleFieldInput() {
|
|||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [matched, prefix, operator, space, keyword] = match
|
||||
if (keyword) {
|
||||
let search = keyword
|
||||
if (operator === 'in' || operator === '?=') {
|
||||
const keywords = keyword.split(',')
|
||||
search = keywords[keywords.length - 1].trim()
|
||||
}
|
||||
if (matched.startsWith('label')) {
|
||||
autocompleteResultType.value = 'labels'
|
||||
autocompleteResults.value = labelStore.filterLabelsByQuery([], keyword)
|
||||
autocompleteResults.value = labelStore.filterLabelsByQuery([], search)
|
||||
}
|
||||
if (matched.startsWith('assignee')) {
|
||||
autocompleteResultType.value = 'assignees'
|
||||
if (projectId) {
|
||||
projectUserService.getAll({projectId}, {s: keyword})
|
||||
projectUserService.getAll({projectId}, {s: search})
|
||||
.then(users => autocompleteResults.value = users.length > 1 ? users : [])
|
||||
} else {
|
||||
userService.getAll({}, {s: keyword})
|
||||
userService.getAll({}, {s: search})
|
||||
.then(users => autocompleteResults.value = users.length > 1 ? users : [])
|
||||
}
|
||||
}
|
||||
if (!projectId && matched.startsWith('project')) {
|
||||
autocompleteResultType.value = 'projects'
|
||||
autocompleteResults.value = projectStore.searchProject(keyword)
|
||||
autocompleteResults.value = projectStore.searchProject(search)
|
||||
}
|
||||
autocompleteMatchText.value = keyword
|
||||
autocompleteMatchPosition.value = prefix.length - 1
|
||||
autocompleteMatchPosition.value = match.index + prefix.length - 1 + keyword.replace(search, '').length
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -211,13 +231,17 @@ function handleFieldInput() {
|
|||
|
||||
function autocompleteSelect(value) {
|
||||
filterQuery.value = filterQuery.value.substring(0, autocompleteMatchPosition.value + 1) +
|
||||
(autocompleteResultType.value === 'labels'
|
||||
? value.title
|
||||
: value.username) +
|
||||
(autocompleteResultType.value === 'assignees'
|
||||
? value.username
|
||||
: value.title) +
|
||||
filterQuery.value.substring(autocompleteMatchPosition.value + autocompleteMatchText.value.length + 1)
|
||||
|
||||
autocompleteResults.value = []
|
||||
}
|
||||
|
||||
// The blur from the textarea might happen before the replacement after autocomplete select was done.
|
||||
// That caused listeners to try and replace values earlier, resulting in broken queries.
|
||||
const blurDebounced = useDebounceFn(() => emit('blur'), 500)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -246,7 +270,7 @@ function autocompleteSelect(value) {
|
|||
@input="handleFieldInput"
|
||||
@focus="onFocusField"
|
||||
@keydown="onKeydown"
|
||||
@blur="e => emit('blur', e)"
|
||||
@blur="blurDebounced"
|
||||
/>
|
||||
<div
|
||||
class="filter-input-highlight"
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
<template>
|
||||
<x-button
|
||||
v-if="hasFilters"
|
||||
variant="secondary"
|
||||
@click="clearFilters"
|
||||
>
|
||||
{{ $t('filters.clear') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
icon="filter"
|
||||
:class="{'has-filters': hasFilters}"
|
||||
@click="() => modalOpen = true"
|
||||
>
|
||||
{{ $t('filters.title') }}
|
||||
|
@ -37,20 +31,38 @@ import {computed, ref, watch} from 'vue'
|
|||
import Filters from '@/components/project/partials/filters.vue'
|
||||
|
||||
import {getDefaultTaskFilterParams, type TaskFilterParams} from '@/services/taskCollection'
|
||||
import {useRouteQuery} from '@vueuse/router'
|
||||
|
||||
const modelValue = defineModel<TaskFilterParams>({})
|
||||
|
||||
const value = ref<TaskFilterParams>({})
|
||||
const filter = useRouteQuery('filter')
|
||||
|
||||
watch(
|
||||
() => modelValue.value,
|
||||
(modelValue: TaskFilterParams) => {
|
||||
value.value = modelValue
|
||||
if (value.value.filter !== '' && value.value.filter !== getDefaultTaskFilterParams().filter) {
|
||||
filter.value = value.value.filter
|
||||
}
|
||||
},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => filter.value,
|
||||
val => {
|
||||
if (modelValue.value?.filter === val || typeof val === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
modelValue.value.filter = val
|
||||
},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
function emitChanges(newValue: TaskFilterParams) {
|
||||
filter.value = newValue.filter
|
||||
if (modelValue.value?.filter === newValue.filter && modelValue.value?.s === newValue.s) {
|
||||
return
|
||||
}
|
||||
|
@ -60,25 +72,11 @@ function emitChanges(newValue: TaskFilterParams) {
|
|||
}
|
||||
|
||||
const hasFilters = computed(() => {
|
||||
// this.value also contains the page parameter which we don't want to include in filters
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const {filter, s} = value.value
|
||||
const def = {...getDefaultTaskFilterParams()}
|
||||
|
||||
const params = {filter, s}
|
||||
const defaultParams = {
|
||||
filter: def.filter,
|
||||
s: s ? def.s : undefined,
|
||||
}
|
||||
|
||||
return JSON.stringify(params) !== JSON.stringify(defaultParams)
|
||||
return value.value.filter !== '' ||
|
||||
value.value.s !== ''
|
||||
})
|
||||
|
||||
const modalOpen = ref(false)
|
||||
|
||||
function clearFilters() {
|
||||
value.value = {...getDefaultTaskFilterParams()}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -89,4 +87,21 @@ function clearFilters() {
|
|||
margin: 2rem 0 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
$filter-bubble-size: .75rem;
|
||||
.has-filters {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: math.div($filter-bubble-size, -2);
|
||||
right: math.div($filter-bubble-size, -2);
|
||||
|
||||
width: $filter-bubble-size;
|
||||
height: $filter-bubble-size;
|
||||
border-radius: 100%;
|
||||
background: var(--primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -25,6 +25,14 @@
|
|||
v-if="hasFooter"
|
||||
#footer
|
||||
>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
class="mr-2"
|
||||
:disabled="params.filter === ''"
|
||||
@click.prevent.stop="clearFiltersAndEmit"
|
||||
>
|
||||
{{ $t('filters.clear') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="primary"
|
||||
@click.prevent.stop="changeAndEmitButton"
|
||||
|
@ -40,8 +48,7 @@ export const ALPHABETICAL_SORT = 'title'
|
|||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, ref} from 'vue'
|
||||
import {watchDebounced} from '@vueuse/core'
|
||||
import {computed, ref, watch} from 'vue'
|
||||
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
||||
import FilterInput from '@/components/project/partials/FilterInput.vue'
|
||||
import {useRoute} from 'vue-router'
|
||||
|
@ -81,18 +88,18 @@ const params = ref<TaskFilterParams>({
|
|||
})
|
||||
|
||||
// Using watchDebounced to prevent the filter re-triggering itself.
|
||||
watchDebounced(
|
||||
watch(
|
||||
() => modelValue,
|
||||
(value: TaskFilterParams) => {
|
||||
const val = {...value}
|
||||
val.filter = transformFilterStringFromApi(
|
||||
val?.filter || '',
|
||||
labelId => labelStore.getLabelById(labelId)?.title,
|
||||
projectId => projectStore.projects.value[projectId]?.title || null,
|
||||
projectId => projectStore.projects[projectId]?.title || null,
|
||||
)
|
||||
params.value = val
|
||||
},
|
||||
{immediate: true, debounce: 500, maxWait: 1000},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
const labelStore = useLabelStore()
|
||||
|
@ -102,7 +109,10 @@ function change() {
|
|||
const filter = transformFilterStringForApi(
|
||||
params.value.filter,
|
||||
labelTitle => labelStore.filterLabelsByQuery([], labelTitle)[0]?.id || null,
|
||||
projectTitle => projectStore.searchProject(projectTitle)[0]?.id || null,
|
||||
projectTitle => {
|
||||
const found = projectStore.findProjectByExactname(projectTitle)
|
||||
return found?.id || null
|
||||
},
|
||||
)
|
||||
|
||||
let s = ''
|
||||
|
@ -130,4 +140,9 @@ function changeAndEmitButton() {
|
|||
change()
|
||||
emit('showResultsButtonClicked')
|
||||
}
|
||||
|
||||
function clearFiltersAndEmit() {
|
||||
params.value.filter = ''
|
||||
changeAndEmitButton()
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, watchEffect, shallowReactive, type ComponentPublicInstance} from 'vue'
|
||||
import {type ComponentPublicInstance, computed, ref, shallowReactive, watchEffect} from 'vue'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {useRouter} from 'vue-router'
|
||||
|
||||
|
@ -107,13 +107,14 @@ import {useTaskStore} from '@/stores/tasks'
|
|||
import {useAuthStore} from '@/stores/auth'
|
||||
|
||||
import {getHistory} from '@/modules/projectHistory'
|
||||
import {parseTaskText, PrefixMode, PREFIXES} from '@/modules/parseTaskText'
|
||||
import {parseTaskText, PREFIXES, PrefixMode} from '@/modules/parseTaskText'
|
||||
import {success} from '@/message'
|
||||
|
||||
import type {ITeam} from '@/modelTypes/ITeam'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
import type {IAbstract} from '@/modelTypes/IAbstract'
|
||||
import {isSavedFilter} from '@/services/savedFilter'
|
||||
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
const router = useRouter()
|
||||
|
@ -280,10 +281,13 @@ const commands = computed<{ [key in COMMAND_TYPE]: Command }>(() => ({
|
|||
|
||||
const placeholder = computed(() => selectedCmd.value?.placeholder || t('quickActions.placeholder'))
|
||||
|
||||
const currentProject = computed(() => Object.keys(baseStore.currentProject).length === 0
|
||||
? null
|
||||
: baseStore.currentProject,
|
||||
)
|
||||
const currentProject = computed(() => {
|
||||
if (Object.keys(baseStore.currentProject).length === 0 || isSavedFilter(baseStore.currentProject)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return baseStore.currentProject
|
||||
})
|
||||
|
||||
const hintText = computed(() => {
|
||||
if (selectedCmd.value !== null && currentProject.value !== null) {
|
||||
|
@ -350,26 +354,6 @@ const isNewTaskCommand = computed(() => (
|
|||
|
||||
const taskSearchTimeout = ref<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
type Filter = { by: string, value: string | number, comparator: string }
|
||||
|
||||
function filtersToParams(filters: Filter[]) {
|
||||
const filter_by: Filter['by'][] = []
|
||||
const filter_value: Filter['value'][] = []
|
||||
const filter_comparator: Filter['comparator'][] = []
|
||||
|
||||
filters.forEach(({by, value, comparator}) => {
|
||||
filter_by.push(by)
|
||||
filter_value.push(value)
|
||||
filter_comparator.push(comparator)
|
||||
})
|
||||
|
||||
return {
|
||||
filter_by,
|
||||
filter_value,
|
||||
filter_comparator,
|
||||
}
|
||||
}
|
||||
|
||||
function searchTasks() {
|
||||
if (
|
||||
searchMode.value !== SEARCH_MODE.ALL &&
|
||||
|
@ -391,40 +375,27 @@ function searchTasks() {
|
|||
|
||||
const {text, project: projectName, labels} = parsedQuery.value
|
||||
|
||||
const filters: Filter[] = []
|
||||
|
||||
// FIXME: improve types
|
||||
function addFilter(
|
||||
by: Filter['by'],
|
||||
value: Filter['value'],
|
||||
comparator: Filter['comparator'],
|
||||
) {
|
||||
filters.push({
|
||||
by,
|
||||
value,
|
||||
comparator,
|
||||
})
|
||||
}
|
||||
let filter = ''
|
||||
|
||||
if (projectName !== null) {
|
||||
const project = projectStore.findProjectByExactname(projectName)
|
||||
console.log({project})
|
||||
if (project !== null) {
|
||||
addFilter('project_id', project.id, 'equals')
|
||||
filter += ' project = ' + project.id
|
||||
}
|
||||
}
|
||||
|
||||
if (labels.length > 0) {
|
||||
const labelIds = labelStore.getLabelsByExactTitles(labels).map((l) => l.id)
|
||||
if (labelIds.length > 0) {
|
||||
addFilter('labels', labelIds.join(), 'in')
|
||||
filter += 'labels in ' + labelIds.join(', ')
|
||||
}
|
||||
}
|
||||
|
||||
const params = {
|
||||
s: text,
|
||||
sort_by: 'done',
|
||||
...filtersToParams(filters),
|
||||
filter,
|
||||
}
|
||||
|
||||
taskSearchTimeout.value = setTimeout(async () => {
|
||||
|
|
|
@ -172,6 +172,7 @@ import Multiselect from '@/components/input/multiselect.vue'
|
|||
import Nothing from '@/components/misc/nothing.vue'
|
||||
import {success} from '@/message'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useConfigStore} from '@/stores/config'
|
||||
|
||||
// FIXME: I think this whole thing can now only manage user/team sharing for projects? Maybe remove a little generalization?
|
||||
|
||||
|
@ -210,8 +211,8 @@ const selectedRight = ref({})
|
|||
const sharables = ref([])
|
||||
const showDeleteModal = ref(false)
|
||||
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const configStore = useConfigStore()
|
||||
const userInfo = computed(() => authStore.info)
|
||||
|
||||
function createShareTypeNameComputed(count: number) {
|
||||
|
@ -360,7 +361,15 @@ async function find(query: string) {
|
|||
found.value = []
|
||||
return
|
||||
}
|
||||
const results = await searchService.getAll({}, {s: query})
|
||||
|
||||
// Include public teams here if we are sharing with teams and its enabled in the config
|
||||
let results = []
|
||||
if (props.shareType === 'team' && configStore.publicTeamsEnabled) {
|
||||
results = await searchService.getAll({}, {s: query, includePublic: true})
|
||||
} else {
|
||||
results = await searchService.getAll({}, {s: query})
|
||||
}
|
||||
|
||||
found.value = results
|
||||
.filter(m => {
|
||||
if(props.shareType === 'user' && m.id === currentUserId.value) {
|
||||
|
|
|
@ -97,6 +97,12 @@
|
|||
editComment()
|
||||
}"
|
||||
/>
|
||||
<Reactions
|
||||
v-model="c.reactions"
|
||||
class="mt-2"
|
||||
entity-kind="comments"
|
||||
:entity-id="c.id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -190,6 +196,7 @@ import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
|||
import {getAvatarUrl, getDisplayName} from '@/models/user'
|
||||
import {useConfigStore} from '@/stores/config'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import Reactions from '@/components/input/Reactions.vue'
|
||||
|
||||
const props = defineProps({
|
||||
taskId: {
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
v-if="showFormSwitch !== null"
|
||||
class="reminder__close-button"
|
||||
:shadow="false"
|
||||
@click="updateDataAndMaybeClose(close)"
|
||||
@click="updateDataAndMaybeCloseNow(close)"
|
||||
>
|
||||
{{ $t('misc.confirm') }}
|
||||
</x-button>
|
||||
|
@ -73,10 +73,10 @@
|
|||
<script setup lang="ts">
|
||||
import {computed, ref, watch} from 'vue'
|
||||
import {SECONDS_A_DAY, SECONDS_A_HOUR} from '@/constants/date'
|
||||
import {IReminderPeriodRelativeTo, REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo'
|
||||
import {type IReminderPeriodRelativeTo, REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import {PeriodUnit, secondsToPeriod} from '@/helpers/time/period'
|
||||
import {type PeriodUnit, secondsToPeriod} from '@/helpers/time/period'
|
||||
import type {ITaskReminder} from '@/modelTypes/ITaskReminder'
|
||||
import {formatDateShort} from '@/helpers/time/formatDate'
|
||||
|
||||
|
@ -87,6 +87,7 @@ import Popup from '@/components/misc/popup.vue'
|
|||
import TaskReminderModel from '@/models/taskReminder'
|
||||
import Card from '@/components/misc/card.vue'
|
||||
import SimpleButton from '@/components/input/SimpleButton.vue'
|
||||
import {useDebounceFn} from '@vueuse/core'
|
||||
|
||||
const {
|
||||
modelValue,
|
||||
|
@ -181,7 +182,9 @@ function setReminderFromPreset(preset, close) {
|
|||
close()
|
||||
}
|
||||
|
||||
function updateDataAndMaybeClose(close) {
|
||||
const updateDataAndMaybeClose = useDebounceFn(updateDataAndMaybeCloseNow, 500)
|
||||
|
||||
function updateDataAndMaybeCloseNow(close) {
|
||||
updateData()
|
||||
if (clearAfterUpdate) {
|
||||
close()
|
||||
|
|
|
@ -71,8 +71,7 @@ import {periodToSeconds, PeriodUnit, secondsToPeriod} from '@/helpers/time/perio
|
|||
import TaskReminderModel from '@/models/taskReminder'
|
||||
|
||||
import type {ITaskReminder} from '@/modelTypes/ITaskReminder'
|
||||
import {REMINDER_PERIOD_RELATIVE_TO_TYPES, type IReminderPeriodRelativeTo} from '@/types/IReminderPeriodRelativeTo'
|
||||
import {useDebounceFn} from '@vueuse/core'
|
||||
import {type IReminderPeriodRelativeTo, REMINDER_PERIOD_RELATIVE_TO_TYPES} from '@/types/IReminderPeriodRelativeTo'
|
||||
|
||||
const {
|
||||
modelValue,
|
||||
|
@ -123,7 +122,7 @@ function updateData() {
|
|||
reminder.value.relativeTo = period.value.relativeTo
|
||||
reminder.value.reminder = null
|
||||
|
||||
useDebounceFn(() => emit('update:modelValue', reminder.value), 1000)
|
||||
emit('update:modelValue', reminder.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -2,10 +2,11 @@ import {ref, shallowReactive, watch, computed, type ComputedGetter} from 'vue'
|
|||
import {useRoute} from 'vue-router'
|
||||
import {useRouteQuery} from '@vueuse/router'
|
||||
|
||||
import TaskCollectionService, {getDefaultTaskFilterParams} from '@/services/taskCollection'
|
||||
import TaskCollectionService, {getDefaultTaskFilterParams, type TaskFilterParams} from '@/services/taskCollection'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import {error} from '@/message'
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
|
||||
export type Order = 'asc' | 'desc' | 'none'
|
||||
|
||||
|
@ -57,7 +58,7 @@ export function useTaskList(projectIdGetter: ComputedGetter<IProject['id']>, sor
|
|||
|
||||
const projectId = computed(() => projectIdGetter())
|
||||
|
||||
const params = ref({...getDefaultTaskFilterParams()})
|
||||
const params = ref<TaskFilterParams>({...getDefaultTaskFilterParams()})
|
||||
|
||||
const search = ref('')
|
||||
const page = useRouteQuery('page', '1', { transform: Number })
|
||||
|
@ -82,10 +83,15 @@ export function useTaskList(projectIdGetter: ComputedGetter<IProject['id']>, sor
|
|||
},
|
||||
)
|
||||
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const getAllTasksParams = computed(() => {
|
||||
return [
|
||||
{projectId: projectId.value},
|
||||
allParams.value,
|
||||
{
|
||||
...allParams.value,
|
||||
filter_timezone: authStore.settings.timezone,
|
||||
},
|
||||
page.value,
|
||||
]
|
||||
})
|
||||
|
|
|
@ -18,7 +18,7 @@ describe('Filter Transformation', () => {
|
|||
'labels': 'labels',
|
||||
}
|
||||
|
||||
describe('For api', () => {
|
||||
describe('For API', () => {
|
||||
for (const c in fieldCases) {
|
||||
it('should transform all filter params for ' + c + ' to snake_case', () => {
|
||||
const transformed = transformFilterStringForApi(c + ' = ipsum', nullTitleToIdResolver, nullTitleToIdResolver)
|
||||
|
@ -37,10 +37,7 @@ describe('Filter Transformation', () => {
|
|||
expect(transformed).toBe('labels = 1')
|
||||
})
|
||||
|
||||
it('should correctly resolve multiple labels', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'labels = lorem && dueDate = now && labels = ipsum',
|
||||
(title: string) => {
|
||||
const multipleDummyResolver = (title: string) => {
|
||||
switch (title) {
|
||||
case 'lorem':
|
||||
return 1
|
||||
|
@ -49,13 +46,28 @@ describe('Filter Transformation', () => {
|
|||
default:
|
||||
return null
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
it('should correctly resolve multiple labels', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'labels = lorem && dueDate = now && labels = ipsum',
|
||||
multipleDummyResolver,
|
||||
nullTitleToIdResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('labels = 1 && due_date = now && labels = 2')
|
||||
})
|
||||
|
||||
it('should correctly resolve multiple labels with an in clause', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'labels in lorem, ipsum && dueDate = now',
|
||||
multipleDummyResolver,
|
||||
nullTitleToIdResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('labels in 1, 2 && due_date = now')
|
||||
})
|
||||
|
||||
it('should correctly resolve projects', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'project = lorem',
|
||||
|
@ -70,20 +82,41 @@ describe('Filter Transformation', () => {
|
|||
const transformed = transformFilterStringForApi(
|
||||
'project = lorem && dueDate = now || project = ipsum',
|
||||
nullTitleToIdResolver,
|
||||
(title: string) => {
|
||||
switch (title) {
|
||||
case 'lorem':
|
||||
return 1
|
||||
case 'ipsum':
|
||||
return 2
|
||||
default:
|
||||
return null
|
||||
}
|
||||
},
|
||||
multipleDummyResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project = 1 && due_date = now || project = 2')
|
||||
})
|
||||
|
||||
it('should correctly resolve multiple projects with in', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'project in lorem, ipsum',
|
||||
nullTitleToIdResolver,
|
||||
multipleDummyResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project in 1, 2')
|
||||
})
|
||||
|
||||
it('should resolve projects at the correct position', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'project = pr',
|
||||
nullTitleToIdResolver,
|
||||
(title: string) => 1,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project = 1')
|
||||
})
|
||||
|
||||
it('should resolve project and labels independently', () => {
|
||||
const transformed = transformFilterStringForApi(
|
||||
'project = lorem && labels = ipsum',
|
||||
multipleDummyResolver,
|
||||
multipleDummyResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project = 1 && labels = 2')
|
||||
})
|
||||
})
|
||||
|
||||
describe('To API', () => {
|
||||
|
@ -105,10 +138,7 @@ describe('Filter Transformation', () => {
|
|||
expect(transformed).toBe('labels = lorem')
|
||||
})
|
||||
|
||||
it('should correctly resolve multiple labels', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'labels = 1 && due_date = now && labels = 2',
|
||||
(id: number) => {
|
||||
const multipleIdToTitleResolver = (id: number) => {
|
||||
switch (id) {
|
||||
case 1:
|
||||
return 'lorem'
|
||||
|
@ -117,13 +147,28 @@ describe('Filter Transformation', () => {
|
|||
default:
|
||||
return null
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
it('should correctly resolve multiple labels', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'labels = 1 && due_date = now && labels = 2',
|
||||
multipleIdToTitleResolver,
|
||||
nullIdToTitleResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('labels = lorem && dueDate = now && labels = ipsum')
|
||||
})
|
||||
|
||||
it('should correctly resolve multiple labels in', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'labels in 1, 2',
|
||||
multipleIdToTitleResolver,
|
||||
nullIdToTitleResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('labels in lorem, ipsum')
|
||||
})
|
||||
|
||||
it('should correctly resolve projects', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'project = 1',
|
||||
|
@ -136,21 +181,22 @@ describe('Filter Transformation', () => {
|
|||
|
||||
it('should correctly resolve multiple projects', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'project = lorem && due_date = now || project = ipsum',
|
||||
'project = 1 && due_date = now || project = 2',
|
||||
nullIdToTitleResolver,
|
||||
(id: number) => {
|
||||
switch (id) {
|
||||
case 1:
|
||||
return 'lorem'
|
||||
case 2:
|
||||
return 'ipsum'
|
||||
default:
|
||||
return null
|
||||
}
|
||||
},
|
||||
multipleIdToTitleResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project = lorem && dueDate = now || project = ipsum')
|
||||
})
|
||||
|
||||
it('should correctly resolve multiple projects in', () => {
|
||||
const transformed = transformFilterStringFromApi(
|
||||
'project in 1, 2',
|
||||
nullIdToTitleResolver,
|
||||
multipleIdToTitleResolver,
|
||||
)
|
||||
|
||||
expect(transformed).toBe('project in lorem, ipsum')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -33,6 +33,7 @@ export const AVAILABLE_FILTER_FIELDS = [
|
|||
...DATE_FIELDS,
|
||||
...ASSIGNEE_FIELDS,
|
||||
...LABEL_FIELDS,
|
||||
...PROJECT_FIELDS,
|
||||
]
|
||||
|
||||
export const FILTER_OPERATORS = [
|
||||
|
@ -54,10 +55,10 @@ export const FILTER_JOIN_OPERATOR = [
|
|||
')',
|
||||
]
|
||||
|
||||
export const FILTER_OPERATORS_REGEX = '(<|>|<=|>=|=|!=)'
|
||||
export const FILTER_OPERATORS_REGEX = '(<|>|<=|>=|=|!=|in)'
|
||||
|
||||
function getFieldPattern(field: string): RegExp {
|
||||
return new RegExp('(' + field + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*)([\'"]?)([^\'"&|()]+\\1?)?', 'ig')
|
||||
export function getFilterFieldRegexPattern(field: string): RegExp {
|
||||
return new RegExp('(' + field + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*)([\'"]?)([^\'"&|()<]+\\1?)?', 'ig')
|
||||
}
|
||||
|
||||
export function transformFilterStringForApi(
|
||||
|
@ -72,33 +73,54 @@ export function transformFilterStringForApi(
|
|||
|
||||
// Transform labels to ids
|
||||
LABEL_FIELDS.forEach(field => {
|
||||
const pattern = getFieldPattern(field)
|
||||
const pattern = getFilterFieldRegexPattern(field)
|
||||
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = pattern.exec(filter)) !== null) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [matched, prefix, operator, space, keyword] = match
|
||||
if (keyword) {
|
||||
const labelId = labelResolver(keyword.trim())
|
||||
if (labelId !== null) {
|
||||
filter = filter.replace(keyword, String(labelId))
|
||||
let keywords = [keyword.trim()]
|
||||
if (operator === 'in' || operator === '?=') {
|
||||
keywords = keyword.trim().split(',').map(k => k.trim())
|
||||
}
|
||||
|
||||
keywords.forEach(k => {
|
||||
const labelId = labelResolver(k)
|
||||
if (labelId !== null) {
|
||||
filter = filter.replace(k, String(labelId))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
// Transform projects to ids
|
||||
PROJECT_FIELDS.forEach(field => {
|
||||
const pattern = getFieldPattern(field)
|
||||
const pattern = getFilterFieldRegexPattern(field)
|
||||
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = pattern.exec(filter)) !== null) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [matched, prefix, operator, space, keyword] = match
|
||||
if (keyword) {
|
||||
const projectId = projectResolver(keyword.trim())
|
||||
if (projectId !== null) {
|
||||
filter = filter.replace(keyword, String(projectId))
|
||||
let keywords = [keyword.trim()]
|
||||
if (operator === 'in' || operator === '?=') {
|
||||
keywords = keyword.trim().split(',').map(k => k.trim())
|
||||
}
|
||||
|
||||
let replaced = keyword
|
||||
|
||||
keywords.forEach(k => {
|
||||
const projectId = projectResolver(k)
|
||||
if (projectId !== null) {
|
||||
replaced = replaced.replace(k, String(projectId))
|
||||
}
|
||||
})
|
||||
|
||||
const actualKeywordStart = (match?.index || 0) + prefix.length
|
||||
filter = filter.substring(0, actualKeywordStart) +
|
||||
replaced +
|
||||
filter.substring(actualKeywordStart + keyword.length)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -128,34 +150,48 @@ export function transformFilterStringFromApi(
|
|||
|
||||
// Transform labels to their titles
|
||||
LABEL_FIELDS.forEach(field => {
|
||||
const pattern = getFieldPattern(field)
|
||||
const pattern = getFilterFieldRegexPattern(field)
|
||||
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = pattern.exec(filter)) !== null) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [matched, prefix, operator, space, keyword] = match
|
||||
if (keyword) {
|
||||
const labelTitle = labelResolver(Number(keyword.trim()))
|
||||
if (labelTitle !== null) {
|
||||
filter = filter.replace(keyword, labelTitle)
|
||||
let keywords = [keyword.trim()]
|
||||
if (operator === 'in' || operator === '?=') {
|
||||
keywords = keyword.trim().split(',').map(k => k.trim())
|
||||
}
|
||||
|
||||
keywords.forEach(k => {
|
||||
const labelTitle = labelResolver(parseInt(k))
|
||||
if (labelTitle !== null) {
|
||||
filter = filter.replace(k, labelTitle)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Transform projects to ids
|
||||
PROJECT_FIELDS.forEach(field => {
|
||||
const pattern = getFieldPattern(field)
|
||||
const pattern = getFilterFieldRegexPattern(field)
|
||||
|
||||
let match: RegExpExecArray | null
|
||||
while ((match = pattern.exec(filter)) !== null) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [matched, prefix, operator, space, keyword] = match
|
||||
if (keyword) {
|
||||
const project = projectResolver(Number(keyword.trim()))
|
||||
if (project !== null) {
|
||||
filter = filter.replace(keyword, project)
|
||||
let keywords = [keyword.trim()]
|
||||
if (operator === 'in' || operator === '?=') {
|
||||
keywords = keyword.trim().split(',').map(k => k.trim())
|
||||
}
|
||||
|
||||
keywords.forEach(k => {
|
||||
const project = projectResolver(parseInt(k))
|
||||
if (project !== null) {
|
||||
filter = filter.replace(k, project)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "هذا يشمل جميع المهام ولا يمكن التراجع عن هذا الإجراء!",
|
||||
"success": "تم حذف المشروع بنجاح.",
|
||||
"tasksToDelete": "سيؤدي هذا إلى حذف ما يقارب {count} من المهام.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "هذا المشروع لا يحتوي على أي مهام، يمكن حذفه بشكل آمن."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "الفلاتر",
|
||||
"clear": "مسح الفلاتر",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "العنوان",
|
||||
"titlePlaceholder": "عنوان الفلتر المحفوظ هنا…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "تعديل هذا الفلتر المحفوظ",
|
||||
"success": "تم حفظ الفلتر بنجاح."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "إلى",
|
||||
"from": "من",
|
||||
"fromto": "{from} إلى {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "اليوم",
|
||||
"thisWeek": "هذا الأسبوع",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "الشهر الماضي",
|
||||
"thisYear": "هذه السنة",
|
||||
"restOfThisYear": "المتبقي من هذه السنة"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "الوصف",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "مشرف",
|
||||
"member": "عضو"
|
||||
"member": "عضو",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "خطأ",
|
||||
"success": "تم بنجاح",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} to {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Today",
|
||||
"thisWeek": "This Week",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Last Month",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -57,11 +57,11 @@
|
|||
"logout": "Odhlásit se",
|
||||
"emailInvalid": "Prosím zadejte platnou emailovou adresu.",
|
||||
"usernameRequired": "Zadejte prosím uživatelské jméno.",
|
||||
"usernameMustNotContainSpace": "The username must not contain spaces.",
|
||||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"usernameMustNotContainSpace": "Uživatelské jméno nesmí obsahovat mezery.",
|
||||
"usernameMustNotLookLikeUrl": "Uživatelské jméno nesmí vypadat jako adresa URL.",
|
||||
"passwordRequired": "Zadejte prosím heslo.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMin": "Heslo musí mít nejméně 8 znaků.",
|
||||
"passwordNotMax": "Heslo může mít maximálně 250 znaků.",
|
||||
"showPassword": "Ukázat heslo",
|
||||
"hidePassword": "Skrýt heslo",
|
||||
"noAccountYet": "Ještě nemáte účet?",
|
||||
|
@ -248,6 +248,7 @@
|
|||
"text2": "To zahrnuje všechny úkoly a JE TO NEVRATNÉ!",
|
||||
"success": "Projekt byl úspěšně smazán.",
|
||||
"tasksToDelete": "Neodvolatelně tím odstraníme asi {count} úloh.",
|
||||
"tasksAndChildProjectsToDelete": "Chystáte se neodvolatelně odstranit nějaké úkoly (cca {tasks}) a projekty (cca {projects}).",
|
||||
"noTasksToDelete": "Tento projekt neobsahuje žádné úkoly, mělo by být bezpečné ho smazat."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filtry",
|
||||
"clear": "Vymazat filtry",
|
||||
"showResults": "Zobrazit výsledky",
|
||||
"attributes": {
|
||||
"title": "Název",
|
||||
"titlePlaceholder": "Název uloženého filtru přijde sem…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Upravit tento uložený filtr",
|
||||
"success": "Filtr byl úspěšně uložen."
|
||||
},
|
||||
"query": {
|
||||
"title": "Dotaz",
|
||||
"placeholder": "Zadejte vyhledávací nebo filtrační dotaz…",
|
||||
"help": {
|
||||
"intro": "K filtrování úkolů můžete použít syntaxi dotazů podobnou SQL. Dostupná pole pro filtrování zahrnují:",
|
||||
"link": "Jak to funguje?",
|
||||
"canUseDatemath": "Můžete použít matematiku a nastavit relativní data. Pro více informací klikněte na datum v dotazu.",
|
||||
"fields": {
|
||||
"done": "Zda je úkol dokončen nebo ne",
|
||||
"priority": "Úroveň priority úkolu (1-5)",
|
||||
"percentDone": "Procento dokončení úkolu (0-100)",
|
||||
"dueDate": "Datum dokončení úkolu",
|
||||
"startDate": "Datum zahájení úkolu",
|
||||
"endDate": "Datum dokončení úkolu",
|
||||
"doneAt": "Datum a čas, kdy byl úkol dokončen",
|
||||
"assignees": "Přiřazení uživatelé",
|
||||
"labels": "Štítky přiřazené úkolu",
|
||||
"project": "Projekt, do kterého úkol patří (k dispozici pouze pro uložené filtry, ne na úrovni projektu)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "Dostupné operátory pro filtrování zahrnují:",
|
||||
"notEqual": "Nerovná se",
|
||||
"equal": "Rovná se",
|
||||
"greaterThan": "Větší než",
|
||||
"greaterThanOrEqual": "Větší nebo rovno než",
|
||||
"lessThan": "Menší než",
|
||||
"lessThanOrEqual": "Menší nebo rovno než",
|
||||
"like": "Odpovídá vzoru (s použitím zástupného znaku %)",
|
||||
"in": "Odpovídá libovolné hodnotě v seznamu hodnot oddělených čárkou"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "Pro kombinování více podmínek můžete použít tyto logické operátory:",
|
||||
"and": "AND - shoduje se, pokud jsou splněny všechny podmínky",
|
||||
"or": "OR - shoduje se, pokud je splněna alespoň jedna podmínka",
|
||||
"parentheses": "Závorky seskupují podmínky"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Zde jsou některé příklady filtrovacích dotazů:",
|
||||
"priorityEqual": "Odpovídá úkolům s úrovní priority 4",
|
||||
"dueDatePast": "Odpovídá úkolům s termínem dokončení v minulosti",
|
||||
"undoneHighPriority": "Odpovídá nedokončeným úkolům s úrovní priority 3 nebo vyšší",
|
||||
"assigneesIn": "Odpovídá úkolům přiřazeným buď \"uživatel1\", nebo \"uživatel2\"",
|
||||
"priorityOneOrTwoPastDue": "Odpovídá úkolům s prioritou 1 nebo 2 a termínem dokončení v minulosti"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "Do",
|
||||
"from": "Od",
|
||||
"fromto": "{from} – {to}",
|
||||
"date": "Datum",
|
||||
"ranges": {
|
||||
"today": "Dnes",
|
||||
"thisWeek": "Tento týden",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Minulý měsíc",
|
||||
"thisYear": "Tento rok",
|
||||
"restOfThisYear": "Zbytek tohoto roku"
|
||||
},
|
||||
"values": {
|
||||
"now": "Teď",
|
||||
"startOfToday": "Začátek dnes",
|
||||
"endOfToday": "Konec dnes",
|
||||
"beginningOflastWeek": "Začátek minulého týdne",
|
||||
"endOfLastWeek": "Konec posledního týdne",
|
||||
"beginningOfThisWeek": "Začátek tohoto týdne",
|
||||
"endOfThisWeek": "Konec tohoto týdne",
|
||||
"startOfNextWeek": "Začátek příštího týdne",
|
||||
"endOfNextWeek": "Konec tohoto týdne",
|
||||
"in7Days": "Za 7 dní",
|
||||
"beginningOfLastMonth": "Začátek minulého měsíce",
|
||||
"endOfLastMonth": "Konec minulého měsíce",
|
||||
"startOfThisMonth": "Začátek tohoto měsíce",
|
||||
"endOfThisMonth": "Konec tohoto měsíce",
|
||||
"startOfNextMonth": "Začátek příštího měsíce",
|
||||
"endOfNextMonth": "Konec příštího měsíce",
|
||||
"in30Days": "Za 30 dní",
|
||||
"startOfThisYear": "Začátek tohoto roku",
|
||||
"endOfThisYear": "Konec tohoto roku"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -713,7 +783,7 @@
|
|||
"startDate": "Počáteční datum",
|
||||
"title": "Název",
|
||||
"updated": "Aktualizováno",
|
||||
"doneAt": "Done At"
|
||||
"doneAt": "Dokončeno"
|
||||
},
|
||||
"subscription": {
|
||||
"subscribedTaskThroughParentProject": "Zde se nemůžete odhlásit, protože jste přihlášeni k odběru tohoto úkolu prostřednictvím jeho projektu.",
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Popis",
|
||||
"descriptionPlaceholder": "Popište tým, stiskněte '/' pro více možností…",
|
||||
"admin": "Administrátor",
|
||||
"member": "Člen"
|
||||
"member": "Člen",
|
||||
"isPublic": "Veřejný tým",
|
||||
"isPublicDescription": "Učinit tým veřejně dostupným. Pokud je povoleno, každý může s tímto týmem sdílet projekty, i když není přímým členem."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -975,8 +1047,8 @@
|
|||
"share": "Sdílet",
|
||||
"newProject": "Nový projekt",
|
||||
"createProject": "Vytvořit projekt",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantArchiveIsDefault": "Nemůžete archivovat svůj výchozí projekt.",
|
||||
"cantDeleteIsDefault": "Nemůžete smazat svůj výchozí projekt."
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Chyba",
|
||||
"success": "Úspěch",
|
||||
|
@ -1096,7 +1174,7 @@
|
|||
},
|
||||
"about": {
|
||||
"title": "O aplikaci",
|
||||
"version": "Version: {version}"
|
||||
"version": "Verze: {version}"
|
||||
},
|
||||
"time": {
|
||||
"units": {
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filtre",
|
||||
"clear": "Ryd Filtre",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Titel",
|
||||
"titlePlaceholder": "Det gemte filters titel skrives her…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Rediger Dette Gemte Filter",
|
||||
"success": "Filteret blev slettet."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "Til",
|
||||
"from": "Fra",
|
||||
"fromto": "{from} til {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "I dag",
|
||||
"thisWeek": "Denne uge",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Sidste måned",
|
||||
"thisYear": "Dette år",
|
||||
"restOfThisYear": "Resten af dette år"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Beskrivelse",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Administrator",
|
||||
"member": "Medlem"
|
||||
"member": "Medlem",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "d m Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Fejl",
|
||||
"success": "Succes",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Dies umfasst alle Aufgaben und kann NICHT rückgängig gemacht werden!",
|
||||
"success": "Das Projekt wurde erfolgreich gelöscht.",
|
||||
"tasksToDelete": "Dies löscht unwiderruflich ca. {count} Aufgaben.",
|
||||
"tasksAndChildProjectsToDelete": "Dies löscht unwiderruflich ca. {tasks} Aufgaben und {projects} Projekte.",
|
||||
"noTasksToDelete": "Dieses Projekt enthält keine Aufgaben, es kann sicher gelöscht werden."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filter",
|
||||
"clear": "Filter zurücksetzen",
|
||||
"showResults": "Ergebnisse anzeigen",
|
||||
"attributes": {
|
||||
"title": "Titel",
|
||||
"titlePlaceholder": "Einen gespeicherten Filternamen eingeben …",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Diesen gespeicherten Filter bearbeiten",
|
||||
"success": "Filter gespeichert."
|
||||
},
|
||||
"query": {
|
||||
"title": "Abfrage",
|
||||
"placeholder": "Gib eine Suche oder Filterabfrage ein…",
|
||||
"help": {
|
||||
"intro": "Um Aufgaben zu filtern, kannst du eine SQL-ähnliche Abfragesyntax verwenden. Du kannst die folgenden Felder in deinem Filter verwenden:",
|
||||
"link": "Wie funktioniert das?",
|
||||
"canUseDatemath": "Du kannst Date Math verwenden, um relative Daten festzulegen. Klicke auf den Datumswert in einer Abfrage, um mehr zu erfahren.",
|
||||
"fields": {
|
||||
"done": "Ob die Aufgabe erledigt ist oder nicht",
|
||||
"priority": "Die Priorität der Aufgabe (1-5)",
|
||||
"percentDone": "Prozentsatz der Fertigstellung der Aufgabe (0-100)",
|
||||
"dueDate": "Das Fälligkeitsdatum der Aufgabe",
|
||||
"startDate": "Das Startdatum der Aufgabe",
|
||||
"endDate": "Das Enddatum der Aufgabe",
|
||||
"doneAt": "Datum und Uhrzeit, an dem die Aufgabe als erledigt markiert wurde",
|
||||
"assignees": "Die der Aufgabe Zugewiesenen",
|
||||
"labels": "Die der Aufgabe zugeordneten Labels",
|
||||
"project": "Das Projekt, zu dem die Aufgabe gehört (nur verfügbar für gespeicherte Filter, nicht auf Projektebene)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "Die verfügbaren Operatoren für die Filterung sind:",
|
||||
"notEqual": "Ungleich",
|
||||
"equal": "Gleich",
|
||||
"greaterThan": "Größer als",
|
||||
"greaterThanOrEqual": "Größer oder gleich",
|
||||
"lessThan": "Kleiner als",
|
||||
"lessThanOrEqual": "Kleiner als oder gleich",
|
||||
"like": "Vergleicht zu einem Muster (mit Platzhalter %)",
|
||||
"in": "Filtert einen beliebigen Wert in einer kommaseparierten Liste"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "Um mehrere Bedingungen zu kombinieren, kannst du folgende logische Operatoren verwenden:",
|
||||
"and": "UND Operator, stimmt überein, wenn alle Bedingungen wahr sind",
|
||||
"or": "ODER Operator, stimmt überein, wenn eine der Bedingungen wahr ist",
|
||||
"parentheses": "Klammern zum Gruppieren von Bedingungen"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Hier sind einige Beispiele für Filterabfragen:",
|
||||
"priorityEqual": "Findet Aufgaben mit Priorität Level 4",
|
||||
"dueDatePast": "Findet Aufgaben mit einem Fälligkeitsdatum in der Vergangenheit",
|
||||
"undoneHighPriority": "Findet Aufgaben, die nicht erledigt sind und Priorität Level mindestens 3 haben",
|
||||
"assigneesIn": "Findet Aufgaben, die entweder \"user1\" oder \"user2\" zugewiesen sind",
|
||||
"priorityOneOrTwoPastDue": "Findet Aufgaben mit Priorität Level 1 oder 2 und einem Fälligkeitsdatum in der Vergangenheit"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "Bis",
|
||||
"from": "Von",
|
||||
"fromto": "{from} bis {to}",
|
||||
"date": "Datum",
|
||||
"ranges": {
|
||||
"today": "Heute",
|
||||
"thisWeek": "Diese Woche",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Letzter Monat",
|
||||
"thisYear": "Dieses Jahr",
|
||||
"restOfThisYear": "Der Rest des Jahres"
|
||||
},
|
||||
"values": {
|
||||
"now": "Jetzt",
|
||||
"startOfToday": "Beginn des heutigen Tages",
|
||||
"endOfToday": "Ende des heutigen Tages",
|
||||
"beginningOflastWeek": "Beginn der letzten Woche",
|
||||
"endOfLastWeek": "Ende der letzten Woche",
|
||||
"beginningOfThisWeek": "Beginn dieser Woche",
|
||||
"endOfThisWeek": "Ende dieser Woche",
|
||||
"startOfNextWeek": "Beginn der nächsten Woche",
|
||||
"endOfNextWeek": "Ende der nächsten Woche",
|
||||
"in7Days": "In 7 Tagen",
|
||||
"beginningOfLastMonth": "Beginn des letzten Monats",
|
||||
"endOfLastMonth": "Ende des letzten Monats",
|
||||
"startOfThisMonth": "Beginn diesen Monats",
|
||||
"endOfThisMonth": "Ende diesen Monats",
|
||||
"startOfNextMonth": "Beginn des nächsten Monats",
|
||||
"endOfNextMonth": "Ende des nächsten Monats",
|
||||
"in30Days": "In 30 Tagen",
|
||||
"startOfThisYear": "Beginn dieses Jahres",
|
||||
"endOfThisYear": "Ende dieses Jahres"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Beschreibung",
|
||||
"descriptionPlaceholder": "Gib eine Beschreibung für dieses Team ein, drücke '/' für mehr Optionen…",
|
||||
"admin": "Admin",
|
||||
"member": "Mitglied"
|
||||
"member": "Mitglied",
|
||||
"isPublic": "Öffentliches Team",
|
||||
"isPublicDescription": "Machs das Team öffentlich sichtbar. Wenn aktiviert, kann jede:r mit diesem Team Projekte teilen, auch wenn er oder sie kein direktes Mitglied des Teams ist."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} hat mit {value} reagiert",
|
||||
"reactedWithAnd": "{users} und {lastUser} haben mit {value} reagiert",
|
||||
"reactedWithAndMany": "{users} und {num} weitere haben mit {value} reagiert",
|
||||
"add": "Reaktion hinzufügen"
|
||||
},
|
||||
"error": {
|
||||
"error": "Fehler",
|
||||
"success": "Erfolgreich",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Dies umfasst alle Aufgaben und kann NICHT rückgängig gemacht werden!",
|
||||
"success": "Das Projekt wurde erfolgreich gelöscht.",
|
||||
"tasksToDelete": "Dies löscht unwiderruflich ca. {count} Aufgaben.",
|
||||
"tasksAndChildProjectsToDelete": "Dies löscht unwiderruflich ca. {tasks} Aufgaben und {projects} Projekte.",
|
||||
"noTasksToDelete": "Dieses Projekt enthält keine Aufgaben, es kann sicher gelöscht werden."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filter",
|
||||
"clear": "Filter zurücksetzen",
|
||||
"showResults": "Ergebnisse anzeigen",
|
||||
"attributes": {
|
||||
"title": "Titl",
|
||||
"titlePlaceholder": "De Name für de g'speicheret Filter chunt da ahne…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "De g'speicheret Filter bearbeite",
|
||||
"success": "De filter isch erfolgriich g'speichered wore."
|
||||
},
|
||||
"query": {
|
||||
"title": "Abfrage",
|
||||
"placeholder": "Gib eine Suche oder Filterabfrage ein…",
|
||||
"help": {
|
||||
"intro": "Um Aufgaben zu filtern, kannst du eine SQL-ähnliche Abfragesyntax verwenden. Du kannst die folgenden Felder in deinem Filter verwenden:",
|
||||
"link": "Wie funktioniert das?",
|
||||
"canUseDatemath": "Du kannst Date Math verwenden, um relative Daten festzulegen. Klicke auf den Datumswert in einer Abfrage, um mehr zu erfahren.",
|
||||
"fields": {
|
||||
"done": "Ob die Aufgabe erledigt ist oder nicht",
|
||||
"priority": "Die Priorität der Aufgabe (1-5)",
|
||||
"percentDone": "Prozentsatz der Fertigstellung der Aufgabe (0-100)",
|
||||
"dueDate": "Das Fälligkeitsdatum der Aufgabe",
|
||||
"startDate": "Das Startdatum der Aufgabe",
|
||||
"endDate": "Das Enddatum der Aufgabe",
|
||||
"doneAt": "Datum und Uhrzeit, an dem die Aufgabe als erledigt markiert wurde",
|
||||
"assignees": "Die der Aufgabe Zugewiesenen",
|
||||
"labels": "Die der Aufgabe zugeordneten Labels",
|
||||
"project": "Das Projekt, zu dem die Aufgabe gehört (nur verfügbar für gespeicherte Filter, nicht auf Projektebene)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "Die verfügbaren Operatoren für die Filterung sind:",
|
||||
"notEqual": "Ungleich",
|
||||
"equal": "Gleich",
|
||||
"greaterThan": "Größer als",
|
||||
"greaterThanOrEqual": "Größer oder gleich",
|
||||
"lessThan": "Kleiner als",
|
||||
"lessThanOrEqual": "Kleiner als oder gleich",
|
||||
"like": "Vergleicht zu einem Muster (mit Platzhalter %)",
|
||||
"in": "Filtert einen beliebigen Wert in einer kommaseparierten Liste"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "Um mehrere Bedingungen zu kombinieren, kannst du folgende logische Operatoren verwenden:",
|
||||
"and": "UND Operator, stimmt überein, wenn alle Bedingungen wahr sind",
|
||||
"or": "ODER Operator, stimmt überein, wenn eine der Bedingungen wahr ist",
|
||||
"parentheses": "Klammern zum Gruppieren von Bedingungen"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Hier sind einige Beispiele für Filterabfragen:",
|
||||
"priorityEqual": "Findet Aufgaben mit Priorität Level 4",
|
||||
"dueDatePast": "Findet Aufgaben mit einem Fälligkeitsdatum in der Vergangenheit",
|
||||
"undoneHighPriority": "Findet Aufgaben, die nicht erledigt sind und Priorität Level mindestens 3 haben",
|
||||
"assigneesIn": "Findet Aufgaben, die entweder \"user1\" oder \"user2\" zugewiesen sind",
|
||||
"priorityOneOrTwoPastDue": "Findet Aufgaben mit Priorität Level 1 oder 2 und einem Fälligkeitsdatum in der Vergangenheit"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "Bis",
|
||||
"from": "Von",
|
||||
"fromto": "{from} bis {to}",
|
||||
"date": "Datum",
|
||||
"ranges": {
|
||||
"today": "Heute",
|
||||
"thisWeek": "Diese Woche",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Letzter Monat",
|
||||
"thisYear": "Dieses Jahr",
|
||||
"restOfThisYear": "Der Rest des Jahres"
|
||||
},
|
||||
"values": {
|
||||
"now": "Jetzt",
|
||||
"startOfToday": "Beginn des heutigen Tages",
|
||||
"endOfToday": "Ende des heutigen Tages",
|
||||
"beginningOflastWeek": "Beginn der letzten Woche",
|
||||
"endOfLastWeek": "Ende der letzten Woche",
|
||||
"beginningOfThisWeek": "Beginn dieser Woche",
|
||||
"endOfThisWeek": "Ende dieser Woche",
|
||||
"startOfNextWeek": "Beginn der nächsten Woche",
|
||||
"endOfNextWeek": "Ende der nächsten Woche",
|
||||
"in7Days": "In 7 Tagen",
|
||||
"beginningOfLastMonth": "Beginn des letzten Monats",
|
||||
"endOfLastMonth": "Ende des letzten Monats",
|
||||
"startOfThisMonth": "Beginn diesen Monats",
|
||||
"endOfThisMonth": "Ende diesen Monats",
|
||||
"startOfNextMonth": "Beginn des nächsten Monats",
|
||||
"endOfNextMonth": "Ende des nächsten Monats",
|
||||
"in30Days": "In 30 Tagen",
|
||||
"startOfThisYear": "Beginn dieses Jahres",
|
||||
"endOfThisYear": "Ende dieses Jahres"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Beschriibig",
|
||||
"descriptionPlaceholder": "Gib eine Beschreibung für dieses Team ein, drücke '/' für mehr Optionen…",
|
||||
"admin": "Chef",
|
||||
"member": "Mitglied"
|
||||
"member": "Mitglied",
|
||||
"isPublic": "Öffentliches Team",
|
||||
"isPublicDescription": "Machs das Team öffentlich sichtbar. Wenn aktiviert, kann jede:r mit diesem Team Projekte teilen, auch wenn er oder sie kein direktes Mitglied des Teams ist."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} hat mit {value} reagiert",
|
||||
"reactedWithAnd": "{users} und {lastUser} haben mit {value} reagiert",
|
||||
"reactedWithAndMany": "{users} und {num} weitere haben mit {value} reagiert",
|
||||
"add": "Reaktion hinzufügen"
|
||||
},
|
||||
"error": {
|
||||
"error": "Fähler",
|
||||
"success": "Erfolg",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -445,7 +446,7 @@
|
|||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a list"
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
|
@ -986,7 +987,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1093,6 +1096,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} to {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Today",
|
||||
"thisWeek": "This Week",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Last Month",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "¡Esto incluye todas las tareas y NO SE PUEDE DESHACER!",
|
||||
"success": "El proyecto se eliminó con éxito.",
|
||||
"tasksToDelete": "Esto eliminará de forma definitiva aprox. {count} tareas.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "Este proyecto no contiene tareas. Debería ser seguro eliminarlo."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filtros",
|
||||
"clear": "Limpiar Filtros",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Título",
|
||||
"titlePlaceholder": "El título del filtro guardado va aquí…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Editar este Filtro Guardado",
|
||||
"success": "El filtro se guardó con éxito."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "Para",
|
||||
"from": "De",
|
||||
"fromto": "De {from} para {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Hoy",
|
||||
"thisWeek": "Esta Semana",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "El Mes Pasado",
|
||||
"thisYear": "Este Año",
|
||||
"restOfThisYear": "El Resto del Año"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Descripción",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Miembro"
|
||||
"member": "Miembro",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Éxito",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Ceci inclut toutes les tâches et NE PEUT PAS ÊTRE ANNULÉ !",
|
||||
"success": "Le projet a bien été supprimé.",
|
||||
"tasksToDelete": "Cela supprimera définitivement environ {count} tâches.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "Ce projet ne contient aucune tâche, vous pouvez le supprimer sans problème."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filtres",
|
||||
"clear": "Effacer les filtres",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Nom",
|
||||
"titlePlaceholder": "Entre un nom de filtre enregistré…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Modifier ce filtre enregistré",
|
||||
"success": "Filtre enregistré."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "À",
|
||||
"from": "De",
|
||||
"fromto": "Du {from} au {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Aujourd’hui",
|
||||
"thisWeek": "Cette semaine",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Le mois dernier",
|
||||
"thisYear": "Cette année",
|
||||
"restOfThisYear": "Le reste de cette année"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Membre"
|
||||
"member": "Membre",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Erreur",
|
||||
"success": "Succès",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Ez magában foglalja az összes feladatot és NEM VISSZAVONHATÓ!",
|
||||
"success": "A projekt sikeresen törölve.",
|
||||
"tasksToDelete": "Ezzel visszavonhatatlanul eltávolítjuk kb. {count} feladatát.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "Ez a projekt nem tartalmaz feladatokat, biztonságosan törölhető."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Szűrők",
|
||||
"clear": "Szűrők törlése",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Cím",
|
||||
"titlePlaceholder": "A mentett szűrő címe ide kerül…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Mentett szűrő szerkesztése",
|
||||
"success": "A szűrőt sikeresen mentette."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "Eddig",
|
||||
"from": "Ettől",
|
||||
"fromto": "{from} - tól {to} - ig",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Ma",
|
||||
"thisWeek": "Ezen a héten",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Előző hónap",
|
||||
"thisYear": "Aktuális év",
|
||||
"restOfThisYear": "Az év hátralévő része"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Leírás",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Adminisztrátor",
|
||||
"member": "Tag"
|
||||
"member": "Tag",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Hiba",
|
||||
"success": "Siker",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filtri",
|
||||
"clear": "Pulisci Filtri",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Titolo",
|
||||
"titlePlaceholder": "Il titolo del filtro salvato va qui…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Modifica Questo Filtro Salvato",
|
||||
"success": "Filtro salvato."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "A",
|
||||
"from": "Da",
|
||||
"fromto": "da {from} a {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Oggi",
|
||||
"thisWeek": "Questa Settimana",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Mese scorso",
|
||||
"thisYear": "Quest'anno",
|
||||
"restOfThisYear": "Il resto di quest'anno"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Descrizione",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Amministratore",
|
||||
"member": "Membro"
|
||||
"member": "Membro",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Errore",
|
||||
"success": "Fatto",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "このプロジェクトに含まれるタスクはすべて削除されます。この操作は元に戻せません。",
|
||||
"success": "プロジェクトは正常に削除されました。",
|
||||
"tasksToDelete": "約{count}件のタスクが抹消されます。",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "このプロジェクトにはタスクが含まれていないので問題なく削除できます。"
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "絞り込み",
|
||||
"clear": "絞り込みの解除",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "絞り込み条件名",
|
||||
"titlePlaceholder": "絞り込み条件名を入力…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "絞り込み条件の編集",
|
||||
"success": "絞り込み条件は正常に保存されました。"
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "タスクの終了日",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "終了",
|
||||
"from": "開始",
|
||||
"fromto": "{from} 〜 {to} まで",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "今日",
|
||||
"thisWeek": "今週",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "先月",
|
||||
"thisYear": "今年",
|
||||
"restOfThisYear": "今から年末まで"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "今日の終わり",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "先週の終わり",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "今週の終わり",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "来週の終わり",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "先月の終わり",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "今月の終わり",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "来月の終わり",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "今年の終わり"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "説明",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "管理者",
|
||||
"member": "メンバー"
|
||||
"member": "メンバー",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "Y/n/j H:i",
|
||||
"altFormatShort": "Y/n/j"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "프로젝트가 성공적으로 삭제되었습니다.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "필터",
|
||||
"clear": "필터 초기화",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "제목",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} 에서 {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "오늘",
|
||||
"thisWeek": "이번 주",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "지난달",
|
||||
"thisYear": "올해",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Titel",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "Het filter is succesvol opgeslagen."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} to {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Today",
|
||||
"thisWeek": "This Week",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Last Month",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Beschrijving",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Beheerder",
|
||||
"member": "Lid"
|
||||
"member": "Lid",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Fout",
|
||||
"success": "Succes",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Dette inkluderer alle oppgaver og KAN IKKE ANGRES!",
|
||||
"success": "Prosjektet ble slettet.",
|
||||
"tasksToDelete": "Dette vil ugjenkallelig fjerne ca. {count} oppgaver.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "Dette prosjektet inneholder ingen oppgaver, det bør være trygt å slette."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filtre",
|
||||
"clear": "Fjern filtre",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Tittel",
|
||||
"titlePlaceholder": "Den lagrede filtertittelen kommer hit…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Rediger dette lagrede filteret",
|
||||
"success": "Filteret ble lagret."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "Til",
|
||||
"from": "Fra",
|
||||
"fromto": "{from} til {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Idag",
|
||||
"thisWeek": "Denne uken",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Forrige måned",
|
||||
"thisYear": "Dette året",
|
||||
"restOfThisYear": "Resten av denne uken"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Beskrivelse",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Administrator",
|
||||
"member": "Medlem"
|
||||
"member": "Medlem",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "d.m.Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Feil",
|
||||
"success": "Suksess",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Dotyczy to wszystkich zadań i tego NIE DA SIĘ COFNĄĆ!",
|
||||
"success": "Projekt został pomyślnie usunięty.",
|
||||
"tasksToDelete": "To nieodwracalnie usunie około {count} zadań.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "Ten projekt nie zawiera żadnych zadań, więc można go bezpiecznie usunąć."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filtry",
|
||||
"clear": "Wyczyść filtry",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Tytuł",
|
||||
"titlePlaceholder": "Tu wpisz tytuł filtra stałego…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Edytuj ten filtr stały",
|
||||
"success": "Filtr został pomyślnie zapisany."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "Do",
|
||||
"from": "Od",
|
||||
"fromto": "{from} do {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Dziś",
|
||||
"thisWeek": "W tym tygodniu",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Zeszły miesiąc",
|
||||
"thisYear": "Ten rok",
|
||||
"restOfThisYear": "Reszta tego roku"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Opis",
|
||||
"descriptionPlaceholder": "Opisz tutaj zespół, naciśnij '/' aby uzyskać więcej opcji…",
|
||||
"admin": "Administrator",
|
||||
"member": "Członek"
|
||||
"member": "Członek",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Błąd",
|
||||
"success": "Sukces",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Isso inclui todas as tarefas e NÃO PODE SER DESFEITO!",
|
||||
"success": "Seu projeto foi excluído com sucesso.",
|
||||
"tasksToDelete": "Isto irá remover permanentemente approx. {count} tarefas.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "Este projeto não contém tarefas, é seguro excluir."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filtros",
|
||||
"clear": "Limpar Filtros",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Título",
|
||||
"titlePlaceholder": "O título do filtro salvo fica aqui…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Editar este filtro salvo",
|
||||
"success": "O filtro foi salvo com sucesso."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "Para",
|
||||
"from": "De",
|
||||
"fromto": "{from} até {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Hoje",
|
||||
"thisWeek": "Esta semana",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Último mês",
|
||||
"thisYear": "Este ano",
|
||||
"restOfThisYear": "O resto deste ano"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Descrição",
|
||||
"descriptionPlaceholder": "Descreva a equipe aqui, aperte '/' para mais opções…",
|
||||
"admin": "Administrador",
|
||||
"member": "Membro"
|
||||
"member": "Membro",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Erro",
|
||||
"success": "Sucesso",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Isto inclui todas as tarefas e NÃO PODE SER REVERTIDO!",
|
||||
"success": "O projeto foi eliminado com sucesso.",
|
||||
"tasksToDelete": "Isto irá remover irrevogavelmente aprox. {count} tarefas.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "Este projeto não contém tarefas, deve ser seguro eliminá-lo."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filtros",
|
||||
"clear": "Limpar Filtros",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Título",
|
||||
"titlePlaceholder": "O título do filtro memorizado será aqui…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Editar Este Filtro Memorizado",
|
||||
"success": "O filtro foi memorizado com sucesso."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "Até",
|
||||
"from": "De",
|
||||
"fromto": "{from} até {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Hoje",
|
||||
"thisWeek": "Esta semana",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Mês Passado",
|
||||
"thisYear": "Este Ano",
|
||||
"restOfThisYear": "O Resto Deste Ano"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Descrição",
|
||||
"descriptionPlaceholder": "Descreve aqui a equipa, pressiona '/' para mais opções…",
|
||||
"admin": "Administrador",
|
||||
"member": "Membro"
|
||||
"member": "Membro",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Erro",
|
||||
"success": "Sucesso",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} to {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Today",
|
||||
"thisWeek": "This Week",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Last Month",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "Это включает в себя все задачи, и отменить это будет нельзя!",
|
||||
"success": "Проект успешно удалён.",
|
||||
"tasksToDelete": "Это безвозвратно удалит примерно {count} задач.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "В этом проекте нет никаких задач, можно спокойно удалять."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Фильтры",
|
||||
"clear": "Сбросить фильтры",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Название",
|
||||
"titlePlaceholder": "Введите название сохранённого фильтра…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Изменить этот сохранённый фильтр",
|
||||
"success": "Фильтр сохранён."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "По",
|
||||
"from": "С",
|
||||
"fromto": "С {from} по {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Сегодня",
|
||||
"thisWeek": "Эта неделя",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Прошлый месяц",
|
||||
"thisYear": "Этот год",
|
||||
"restOfThisYear": "Остаток этого года"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Описание",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Администратор",
|
||||
"member": "Участник"
|
||||
"member": "Участник",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Ошибка",
|
||||
"success": "Успех",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} to {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Today",
|
||||
"thisWeek": "This Week",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Last Month",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -57,11 +57,11 @@
|
|||
"logout": "Odjava",
|
||||
"emailInvalid": "Prosim vnesite veljaven e-poštni naslov.",
|
||||
"usernameRequired": "Prosim vnesite uporabniško ime.",
|
||||
"usernameMustNotContainSpace": "The username must not contain spaces.",
|
||||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"usernameMustNotContainSpace": "Uporabniško ime ne sme vsebovati presledkov.",
|
||||
"usernameMustNotLookLikeUrl": "Uporabniško ime ne sme izgledati kot URL.",
|
||||
"passwordRequired": "Prosim vnesite geslo.",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMin": "Geslo mora imeti vsaj 8 znakov.",
|
||||
"passwordNotMax": "Geslo mora imeti največ 250 znakov.",
|
||||
"showPassword": "Prikažite geslo",
|
||||
"hidePassword": "Skrijte geslo",
|
||||
"noAccountYet": "Še nimate računa?",
|
||||
|
@ -248,6 +248,7 @@
|
|||
"text2": "To vključuje vse naloge in GA NI MOGOČE RAZVELJAVITI!",
|
||||
"success": "Projekt je bil uspešno izbrisan.",
|
||||
"tasksToDelete": "S tem bo nepreklicno odstranjeno približno {count} nalog.",
|
||||
"tasksAndChildProjectsToDelete": "S tem bo nepreklicno odstranjeno cca. {tasks} nalog in {projects} projektov.",
|
||||
"noTasksToDelete": "Ta projekt ne vsebuje nobenih nalog, zato ga lahko varno izbrišete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filtri",
|
||||
"clear": "Počisti filtre",
|
||||
"showResults": "Prikaži rezultate",
|
||||
"attributes": {
|
||||
"title": "Naslov",
|
||||
"titlePlaceholder": "Tu je naslov shranjenega filtra…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Uredi shranjeni filter",
|
||||
"success": "Filter je bil uspešno shranjen."
|
||||
},
|
||||
"query": {
|
||||
"title": "Poizvedba",
|
||||
"placeholder": "Vnesite poizvedbo za iskanje ali filtriranje…",
|
||||
"help": {
|
||||
"intro": "Za filtriranje nalog lahko uporabite sintakso poizvedbe, podobno SQL. Razpoložljiva polja za filtriranje vključujejo:",
|
||||
"link": "Kako to deluje?",
|
||||
"canUseDatemath": "Za nastavitev relativnih datumov lahko določite datume. Če želite izvedeti več, kliknite vrednost datuma v poizvedbi.",
|
||||
"fields": {
|
||||
"done": "Ali je naloga opravljena ali ne",
|
||||
"priority": "Stopnja prioritete naloge (1-5)",
|
||||
"percentDone": "Odstotek dokončanja naloge (0–100)",
|
||||
"dueDate": "Datum zapadlosti naloge",
|
||||
"startDate": "Začetni datum opravila",
|
||||
"endDate": "Končni datum opravila",
|
||||
"doneAt": "Datum in čas, ko je bila naloga opravljena",
|
||||
"assignees": "Dodeljeni k nalogi",
|
||||
"labels": "Oznake, povezane z opravilom",
|
||||
"project": "Projekt, ki mu naloga pripada (na voljo samo za shranjene filtre, ne na ravni projekta)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "Razpoložljivi operaterji za filtriranje vključujejo:",
|
||||
"notEqual": "Ni enako",
|
||||
"equal": "Je enako",
|
||||
"greaterThan": "Večje kot",
|
||||
"greaterThanOrEqual": "Večje ali enako kot",
|
||||
"lessThan": "Manj kot",
|
||||
"lessThanOrEqual": "Manjše ali enako kot",
|
||||
"like": "Ujema se z vzorcem (z nadomestnimi znaki %)",
|
||||
"in": "Ujema se s katero koli vrednostjo na seznamu vrednosti, ločenih z vejico"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "Če želite združiti več pogojev, lahko uporabite naslednje logične operatorje:",
|
||||
"and": "IN operator, se ujema, če so izpolnjeni vsi pogoji",
|
||||
"or": "ALI operator, se ujema, če je kateri od pogojev resničen",
|
||||
"parentheses": "Oklepaji za združevanje pogojev"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Tukaj je nekaj primerov filtrirnih poizvedb:",
|
||||
"priorityEqual": "Ujema se z nalogami s prioritetno stopnjo 4",
|
||||
"dueDatePast": "Ujema se z opravili z rokom v preteklosti",
|
||||
"undoneHighPriority": "Ujema se z neopravljenimi opravili s prioritetno stopnjo 3 ali višjo",
|
||||
"assigneesIn": "Ujema se z nalogami, dodeljenimi k \"user1\" ali \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Ujema se z opravili s prioritetno stopnjo 1 ali 2 in rokom v preteklosti"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "Za",
|
||||
"from": "Od",
|
||||
"fromto": "{from} do {to}",
|
||||
"date": "Datum",
|
||||
"ranges": {
|
||||
"today": "Danes",
|
||||
"thisWeek": "Ta teden",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Prejšnji mesec",
|
||||
"thisYear": "Letos",
|
||||
"restOfThisYear": "Preostanek tega leta"
|
||||
},
|
||||
"values": {
|
||||
"now": "Zdaj",
|
||||
"startOfToday": "Začetek današnjega dne",
|
||||
"endOfToday": "Konec današnjega dne",
|
||||
"beginningOflastWeek": "Začetek prejšnjega tedna",
|
||||
"endOfLastWeek": "Konec prejšnjega tedna",
|
||||
"beginningOfThisWeek": "Začetek tega tedna",
|
||||
"endOfThisWeek": "Konec tega tedna",
|
||||
"startOfNextWeek": "Začetek naslednjega tedna",
|
||||
"endOfNextWeek": "Konec naslednjega tedna",
|
||||
"in7Days": "V 7 dneh",
|
||||
"beginningOfLastMonth": "Začetek prejšnjega meseca",
|
||||
"endOfLastMonth": "Konec prejšnjega meseca",
|
||||
"startOfThisMonth": "Začetek tega meseca",
|
||||
"endOfThisMonth": "Konec tega meseca",
|
||||
"startOfNextMonth": "Začetek naslednjega meseca",
|
||||
"endOfNextMonth": "Konec naslednjega meseca",
|
||||
"in30Days": "V 30 dneh",
|
||||
"startOfThisYear": "Začetek tega leta",
|
||||
"endOfThisYear": "Konec tega leta"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -713,7 +783,7 @@
|
|||
"startDate": "Začetni datum",
|
||||
"title": "Naslov",
|
||||
"updated": "Posodobljeno",
|
||||
"doneAt": "Done At"
|
||||
"doneAt": "Končano ob"
|
||||
},
|
||||
"subscription": {
|
||||
"subscribedTaskThroughParentProject": "Ker ste na to nalogo naročeni prek njenega projekta, se tu ne morete odjaviti.",
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Opis",
|
||||
"descriptionPlaceholder": "Tukaj opiši ekipo, pritisni '/' za več možnosti…",
|
||||
"admin": "Administrator",
|
||||
"member": "Član"
|
||||
"member": "Član",
|
||||
"isPublic": "Javna ekipa",
|
||||
"isPublicDescription": "Naj bo ekipa javno vidna. Ko je omogočeno, lahko vsakdo deli projekte s to ekipo, tudi če ni neposredni član."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -975,8 +1047,8 @@
|
|||
"share": "Skupna raba",
|
||||
"newProject": "Nov projekt",
|
||||
"createProject": "Ustvari projekt",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantArchiveIsDefault": "Tega ne morete arhivirati, ker je to vaš privzeti projekt.",
|
||||
"cantDeleteIsDefault": "Tega ne morete izbrisati, ker je to vaš privzeti projekt."
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} se je odzval z {value}",
|
||||
"reactedWithAnd": "{users} in {lastUser} sta reagirala z {value}",
|
||||
"reactedWithAndMany": "{users} in {num} drugih se je odzvalo z {value}",
|
||||
"add": "Dodaj svoj odziv"
|
||||
},
|
||||
"error": {
|
||||
"error": "Napaka",
|
||||
"success": "Uspeh",
|
||||
|
@ -1096,7 +1174,7 @@
|
|||
},
|
||||
"about": {
|
||||
"title": "O programu",
|
||||
"version": "Version: {version}"
|
||||
"version": "Verzija: {version}"
|
||||
},
|
||||
"time": {
|
||||
"units": {
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} to {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Today",
|
||||
"thisWeek": "This Week",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Last Month",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filter",
|
||||
"clear": "Rensa filter",
|
||||
"showResults": "Visa resultat",
|
||||
"attributes": {
|
||||
"title": "Titel",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Lika med",
|
||||
"greaterThan": "Större än",
|
||||
"greaterThanOrEqual": "Större än eller lika med",
|
||||
"lessThan": "Mindre än",
|
||||
"lessThanOrEqual": "Mindre än eller lika med",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "Till",
|
||||
"from": "Från",
|
||||
"fromto": "{from} till {to}",
|
||||
"date": "Datum",
|
||||
"ranges": {
|
||||
"today": "I dag",
|
||||
"thisWeek": "Denna vecka",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Förra månaden",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Nu",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Beskrivning",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Medlem"
|
||||
"member": "Medlem",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} to {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Today",
|
||||
"thisWeek": "This Week",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Last Month",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Bộ lọc",
|
||||
"clear": "Xoá các bộ lọc",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Tiêu đề",
|
||||
"titlePlaceholder": "Tiêu đề bộ lọc đã lưu ở đây…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Sửa bộ lọc sẵn này",
|
||||
"success": "Bộ lọc đã được lưu thành công."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "Đến",
|
||||
"from": "Từ",
|
||||
"fromto": "{from} đến {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Hôm nay",
|
||||
"thisWeek": "Tuần này",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Tháng trước",
|
||||
"thisYear": "Năm nay",
|
||||
"restOfThisYear": "Toàn bộ ngày còn lại trong năm"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Mô tả",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Quản trị viên",
|
||||
"member": "Thành viên"
|
||||
"member": "Thành viên",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Lỗi",
|
||||
"success": "Thành công",
|
||||
|
|
|
@ -57,11 +57,11 @@
|
|||
"logout": "注销",
|
||||
"emailInvalid": "请输入有效的电子邮件地址。",
|
||||
"usernameRequired": "请输入用户名",
|
||||
"usernameMustNotContainSpace": "The username must not contain spaces.",
|
||||
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
|
||||
"usernameMustNotContainSpace": "用户名不能包含空格。",
|
||||
"usernameMustNotLookLikeUrl": "用户名不能像一个 URL。",
|
||||
"passwordRequired": "请提供密码",
|
||||
"passwordNotMin": "Password must have at least 8 characters.",
|
||||
"passwordNotMax": "Password must have at most 250 characters.",
|
||||
"passwordNotMin": "密码至少有8个字符",
|
||||
"passwordNotMax": "密码不能超过250个字符",
|
||||
"showPassword": "显示密码",
|
||||
"hidePassword": "隐藏密码",
|
||||
"noAccountYet": "还没有账号?",
|
||||
|
@ -248,6 +248,7 @@
|
|||
"text2": "这包括所有的任务,并且无法撤销!",
|
||||
"success": "项目已成功删除。",
|
||||
"tasksToDelete": "此操作无法撤消!将移除大约 {count} 个任务。",
|
||||
"tasksAndChildProjectsToDelete": "删除将不可挽回。 {tasks} 任务和 {projects} 项目。",
|
||||
"noTasksToDelete": "此列表不包含任何任务,可以安全删除。"
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "筛选器",
|
||||
"clear": "清除筛选条件",
|
||||
"showResults": "显示结果",
|
||||
"attributes": {
|
||||
"title": "标题",
|
||||
"titlePlaceholder": "填写筛选器标题",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "编辑此保存的过滤器",
|
||||
"success": "过滤器保存成功。"
|
||||
},
|
||||
"query": {
|
||||
"title": "查询",
|
||||
"placeholder": "输入搜索或过滤查询…",
|
||||
"help": {
|
||||
"intro": "要过滤任务,您可以使用类似于SQL的查询语法。可用的过滤字段包括:",
|
||||
"link": "这是如何运作的?",
|
||||
"canUseDatemath": "您可以将数学日期设定为相对日期。点击查询中的日期值来查找更多信息。",
|
||||
"fields": {
|
||||
"done": "任务是否完成",
|
||||
"priority": "任务的优先级(1-5)",
|
||||
"percentDone": "任务完成百分比 (0-100)",
|
||||
"dueDate": "任务的截止日期",
|
||||
"startDate": "任务的开始日期",
|
||||
"endDate": "任务结束日期",
|
||||
"doneAt": "任务完成的日期和时间",
|
||||
"assignees": "任务的指派人",
|
||||
"labels": "与任务相关的标签",
|
||||
"project": "任务属于的项目 (仅适用于保存的过滤器,不适用于项目级别)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "可用的过滤操作员包括:",
|
||||
"notEqual": "不等于",
|
||||
"equal": "等于",
|
||||
"greaterThan": "大于",
|
||||
"greaterThanOrEqual": "大于或等于",
|
||||
"lessThan": "小于",
|
||||
"lessThanOrEqual": "小于或等于",
|
||||
"like": "匹配模式 (使用通配符%)",
|
||||
"in": "匹配任意一个逗号分隔的值列表"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "若要合并多个条件,您可以使用以下逻辑运算符:",
|
||||
"and": "和操作者,如果所有条件都是真实的,匹配的",
|
||||
"or": "或操作员,匹配任何条件都是正确的",
|
||||
"parentheses": "分组条件的括号:"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "以下是一些过滤查询示例:",
|
||||
"priorityEqual": "匹配任务优先级 4",
|
||||
"dueDatePast": "匹配任务与过去到期日期",
|
||||
"undoneHighPriority": "匹配取消优先级别3或更高的任务",
|
||||
"assigneesIn": "匹配分配给\"user1\" 或 \"user2\" 的任务",
|
||||
"priorityOneOrTwoPastDue": "匹配优先级别1或2以及过去到期的任务"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "到",
|
||||
"from": "开始",
|
||||
"fromto": "{from} 到 {to}",
|
||||
"date": "日期",
|
||||
"ranges": {
|
||||
"today": "今天",
|
||||
"thisWeek": "本周",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "上个月",
|
||||
"thisYear": "今年",
|
||||
"restOfThisYear": "本年度剩余时间"
|
||||
},
|
||||
"values": {
|
||||
"now": "现在",
|
||||
"startOfToday": "今天开始",
|
||||
"endOfToday": "今天结束",
|
||||
"beginningOflastWeek": "上周开始",
|
||||
"endOfLastWeek": "上周结束",
|
||||
"beginningOfThisWeek": "本周开始",
|
||||
"endOfThisWeek": "本周结束",
|
||||
"startOfNextWeek": "下周开始",
|
||||
"endOfNextWeek": "下周结束",
|
||||
"in7Days": "7 天内",
|
||||
"beginningOfLastMonth": "上月开始",
|
||||
"endOfLastMonth": "上个月底",
|
||||
"startOfThisMonth": "本月开始",
|
||||
"endOfThisMonth": "这个月底",
|
||||
"startOfNextMonth": "下月开始",
|
||||
"endOfNextMonth": "下个月底",
|
||||
"in30Days": "30 天内",
|
||||
"startOfThisYear": "今年开始",
|
||||
"endOfThisYear": "今年年底"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -713,7 +783,7 @@
|
|||
"startDate": "开始日期",
|
||||
"title": "标题",
|
||||
"updated": "已更新",
|
||||
"doneAt": "Done At"
|
||||
"doneAt": "完成于"
|
||||
},
|
||||
"subscription": {
|
||||
"subscribedTaskThroughParentProject": "你无法在此处取消订阅,因为你已通过其项目订阅了此任务。",
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "描述信息",
|
||||
"descriptionPlaceholder": "在此描述团队,点击'/'获取更多选项…",
|
||||
"admin": "管理员",
|
||||
"member": "成员"
|
||||
"member": "成员",
|
||||
"isPublic": "公开团队",
|
||||
"isPublicDescription": "让团队公开发现。如果启用,任何人都可以与这个团队分享项目,即使不是直接成员。"
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -975,8 +1047,8 @@
|
|||
"share": "共享",
|
||||
"newProject": "新项目",
|
||||
"createProject": "创建项目",
|
||||
"cantArchiveIsDefault": "You cannot archive this because it is your default project.",
|
||||
"cantDeleteIsDefault": "You cannot delete this because it is your default project."
|
||||
"cantArchiveIsDefault": "您不能归档,因为这是您的默认项目。",
|
||||
"cantDeleteIsDefault": "您不能删除这个项目,因为这是您的默认项目。"
|
||||
},
|
||||
"apiConfig": {
|
||||
"url": "Vikunja URL",
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "Y M d H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} {value} 反应",
|
||||
"reactedWithAnd": "{users} 和{lastUser} 的{value} 反应",
|
||||
"reactedWithAndMany": "{users} 和{num} 的{value} 反应",
|
||||
"add": "添加您的反应"
|
||||
},
|
||||
"error": {
|
||||
"error": "错误",
|
||||
"success": "成功",
|
||||
|
@ -1096,7 +1174,7 @@
|
|||
},
|
||||
"about": {
|
||||
"title": "关于",
|
||||
"version": "Version: {version}"
|
||||
"version": "版本:{version}"
|
||||
},
|
||||
"time": {
|
||||
"units": {
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||
"success": "The project was successfully deleted.",
|
||||
"tasksToDelete": "This will irrevocably remove approx. {count} tasks.",
|
||||
"tasksAndChildProjectsToDelete": "This will irrevocably remove approx. {tasks} tasks and {projects} projects.",
|
||||
"noTasksToDelete": "This project does not contain any tasks, it should be safe to delete."
|
||||
},
|
||||
"duplicate": {
|
||||
|
@ -385,6 +386,7 @@
|
|||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"showResults": "Show results",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -415,6 +417,52 @@
|
|||
"edit": {
|
||||
"title": "Edit This Saved Filter",
|
||||
"success": "The filter was saved successfully."
|
||||
},
|
||||
"query": {
|
||||
"title": "Query",
|
||||
"placeholder": "Type a search or filter query…",
|
||||
"help": {
|
||||
"intro": "To filter tasks, you can use a query syntax similar to SQL. The available fields for filtering include:",
|
||||
"link": "How does this work?",
|
||||
"canUseDatemath": "You can date math to set relative dates. Click on the date value in a query to find out more.",
|
||||
"fields": {
|
||||
"done": "Whether the task is completed or not",
|
||||
"priority": "The priority level of the task (1-5)",
|
||||
"percentDone": "The percentage of completion for the task (0-100)",
|
||||
"dueDate": "The due date of the task",
|
||||
"startDate": "The start date of the task",
|
||||
"endDate": "The end date of the task",
|
||||
"doneAt": "The date and time when the task was completed",
|
||||
"assignees": "The assignees of the task",
|
||||
"labels": "The labels associated with the task",
|
||||
"project": "The project the task belongs to (only available for saved filters, not on a project level)"
|
||||
},
|
||||
"operators": {
|
||||
"intro": "The available operators for filtering include:",
|
||||
"notEqual": "Not equal to",
|
||||
"equal": "Equal to",
|
||||
"greaterThan": "Greater than",
|
||||
"greaterThanOrEqual": "Greater than or equal to",
|
||||
"lessThan": "Less than",
|
||||
"lessThanOrEqual": "Less than or equal to",
|
||||
"like": "Matches a pattern (using wildcard %)",
|
||||
"in": "Matches any value in a comma-seperated list of values"
|
||||
},
|
||||
"logicalOperators": {
|
||||
"intro": "To combine multiple conditions, you can use the following logical operators:",
|
||||
"and": "AND operator, matches if all conditions are true",
|
||||
"or": "OR operator, matches if any of the conditions are true",
|
||||
"parentheses": "Parentheses for grouping conditions"
|
||||
},
|
||||
"examples": {
|
||||
"intro": "Here are some examples of filter queries:",
|
||||
"priorityEqual": "Matches tasks with priority level 4",
|
||||
"dueDatePast": "Matches tasks with a due date in the past",
|
||||
"undoneHighPriority": "Matches undone tasks with priority level 3 or higher",
|
||||
"assigneesIn": "Matches tasks assigned to either \"user1\" or \"user2\"",
|
||||
"priorityOneOrTwoPastDue": "Matches tasks with priority level 1 or 2 and a due date in the past"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"migrate": {
|
||||
|
@ -584,6 +632,7 @@
|
|||
"to": "To",
|
||||
"from": "From",
|
||||
"fromto": "{from} to {to}",
|
||||
"date": "Date",
|
||||
"ranges": {
|
||||
"today": "Today",
|
||||
"thisWeek": "This Week",
|
||||
|
@ -598,6 +647,27 @@
|
|||
"lastMonth": "Last Month",
|
||||
"thisYear": "This Year",
|
||||
"restOfThisYear": "The Rest of This Year"
|
||||
},
|
||||
"values": {
|
||||
"now": "Now",
|
||||
"startOfToday": "Start of today",
|
||||
"endOfToday": "End of today",
|
||||
"beginningOflastWeek": "Beginning of last week",
|
||||
"endOfLastWeek": "End of last week",
|
||||
"beginningOfThisWeek": "Beginning of this week",
|
||||
"endOfThisWeek": "End of this week",
|
||||
"startOfNextWeek": "Start of next week",
|
||||
"endOfNextWeek": "End of next week",
|
||||
"in7Days": "In 7 days",
|
||||
"beginningOfLastMonth": "Beginning of last month",
|
||||
"endOfLastMonth": "End of last month",
|
||||
"startOfThisMonth": "Start of this month",
|
||||
"endOfThisMonth": "End of this month",
|
||||
"startOfNextMonth": "Start of next month",
|
||||
"endOfNextMonth": "End of next month",
|
||||
"in30Days": "In 30 days",
|
||||
"startOfThisYear": "Beginning of this year",
|
||||
"endOfThisYear": "End of this year"
|
||||
}
|
||||
},
|
||||
"datemathHelp": {
|
||||
|
@ -916,7 +986,9 @@
|
|||
"description": "Description",
|
||||
"descriptionPlaceholder": "Describe the team here, hit '/' for more options…",
|
||||
"admin": "Admin",
|
||||
"member": "Member"
|
||||
"member": "Member",
|
||||
"isPublic": "Public Team",
|
||||
"isPublicDescription": "Make the team publicly discoverable. When enabled, anyone can share projects with this team even when not being a direct member."
|
||||
}
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
|
@ -1023,6 +1095,12 @@
|
|||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
"reaction": {
|
||||
"reactedWith": "{user} reacted with {value}",
|
||||
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
|
||||
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
|
||||
"add": "Add your reaction"
|
||||
},
|
||||
"error": {
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
|
|
14
frontend/src/modelTypes/IReaction.ts
Normal file
14
frontend/src/modelTypes/IReaction.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import type {IAbstract} from '@/modelTypes/IAbstract'
|
||||
import type {IUser} from '@/modelTypes/IUser'
|
||||
|
||||
export type ReactionKind = 'tasks' | 'comments'
|
||||
|
||||
export interface IReaction extends IAbstract {
|
||||
id: number
|
||||
kind: ReactionKind
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface IReactionPerEntity {
|
||||
[reaction: string]: IUser[]
|
||||
}
|
|
@ -14,6 +14,7 @@ import type {IRepeatMode} from '@/types/IRepeatMode'
|
|||
|
||||
import type {PartialWithId} from '@/types/PartialWithId'
|
||||
import type {ITaskReminder} from '@/modelTypes/ITaskReminder'
|
||||
import type {IReactionPerEntity} from '@/modelTypes/IReaction'
|
||||
|
||||
export interface ITask extends IAbstract {
|
||||
id: number
|
||||
|
@ -46,6 +47,8 @@ export interface ITask extends IAbstract {
|
|||
position: number
|
||||
kanbanPosition: number
|
||||
|
||||
reactions: IReactionPerEntity
|
||||
|
||||
createdBy: IUser
|
||||
created: Date
|
||||
updated: Date
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type {IAbstract} from './IAbstract'
|
||||
import type {IUser} from './IUser'
|
||||
import type {ITask} from './ITask'
|
||||
import type {IReactionPerEntity} from '@/modelTypes/IReaction'
|
||||
|
||||
export interface ITaskComment extends IAbstract {
|
||||
id: number
|
||||
|
@ -8,6 +9,8 @@ export interface ITaskComment extends IAbstract {
|
|||
comment: string
|
||||
author: IUser
|
||||
|
||||
reactions: IReactionPerEntity
|
||||
|
||||
created: Date
|
||||
updated: Date
|
||||
}
|
|
@ -10,6 +10,7 @@ export interface ITeam extends IAbstract {
|
|||
members: ITeamMember[]
|
||||
right: Right
|
||||
oidcId: string
|
||||
isPublic: boolean
|
||||
|
||||
createdBy: IUser
|
||||
created: Date
|
||||
|
|
14
frontend/src/models/reaction.ts
Normal file
14
frontend/src/models/reaction.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import type {IReaction} from '@/modelTypes/IReaction'
|
||||
import AbstractModel from '@/models/abstractModel'
|
||||
|
||||
export default class ReactionModel extends AbstractModel<IReaction> implements IReaction {
|
||||
id: number = 0
|
||||
kind: 'tasks' | 'comments' = 'tasks'
|
||||
value: string = ''
|
||||
|
||||
constructor(data: Partial<IReaction>) {
|
||||
super()
|
||||
this.assignData(data)
|
||||
}
|
||||
}
|
||||
|
|
@ -87,6 +87,8 @@ export default class TaskModel extends AbstractModel<ITask> implements ITask {
|
|||
position = 0
|
||||
kanbanPosition = 0
|
||||
|
||||
reactions = {}
|
||||
|
||||
createdBy: IUser = UserModel
|
||||
created: Date = null
|
||||
updated: Date = null
|
||||
|
@ -148,6 +150,12 @@ export default class TaskModel extends AbstractModel<ITask> implements ITask {
|
|||
this.updated = new Date(this.updated)
|
||||
|
||||
this.projectId = Number(this.projectId)
|
||||
|
||||
// We can't convert emojis to camel case, hence we do this manually
|
||||
this.reactions = {}
|
||||
Object.keys(data.reactions || {}).forEach(reaction => {
|
||||
this.reactions[reaction] = data.reactions[reaction].map(u => new UserModel(u))
|
||||
})
|
||||
}
|
||||
|
||||
getTextIdentifier() {
|
||||
|
|
|
@ -11,6 +11,8 @@ export default class TaskCommentModel extends AbstractModel<ITaskComment> implem
|
|||
comment = ''
|
||||
author: IUser = UserModel
|
||||
|
||||
reactions = {}
|
||||
|
||||
created: Date = null
|
||||
updated: Date = null
|
||||
|
||||
|
@ -21,5 +23,11 @@ export default class TaskCommentModel extends AbstractModel<ITaskComment> implem
|
|||
this.author = new UserModel(this.author)
|
||||
this.created = new Date(this.created)
|
||||
this.updated = new Date(this.updated)
|
||||
|
||||
// We can't convert emojis to camel case, hence we do this manually
|
||||
this.reactions = {}
|
||||
Object.keys(data.reactions || {}).forEach(reaction => {
|
||||
this.reactions[reaction] = data.reactions[reaction].map(u => new UserModel(u))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ export default class TeamModel extends AbstractModel<ITeam> implements ITeam {
|
|||
members: ITeamMember[] = []
|
||||
right: Right = RIGHTS.READ
|
||||
oidcId = ''
|
||||
isPublic: boolean = false
|
||||
|
||||
createdBy: IUser = {} // FIXME: seems wrong
|
||||
created: Date = null
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'virtual:vite-plugin-sentry/sentry-config'
|
||||
import type {App} from 'vue'
|
||||
import type {Router} from 'vue-router'
|
||||
import {AxiosError} from 'axios'
|
||||
|
||||
export default async function setupSentry(app: App, router: Router) {
|
||||
const Sentry = await import('@sentry/vue')
|
||||
|
@ -18,5 +19,15 @@ export default async function setupSentry(app: App, router: Router) {
|
|||
}),
|
||||
],
|
||||
tracesSampleRate: 1.0,
|
||||
beforeSend(event, hint) {
|
||||
|
||||
if ((typeof hint.originalException?.code !== 'undefined' &&
|
||||
typeof hint.originalException?.message !== 'undefined')
|
||||
|| hint.originalException instanceof AxiosError) {
|
||||
return null
|
||||
}
|
||||
|
||||
return event
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -77,20 +77,26 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
|
|||
case 'post':
|
||||
if (this.useUpdateInterceptor()) {
|
||||
config.data = this.beforeUpdate(config.data)
|
||||
if(this.autoTransformBeforePost()) {
|
||||
config.data = objectToSnakeCase(config.data)
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'put':
|
||||
if (this.useCreateInterceptor()) {
|
||||
config.data = this.beforeCreate(config.data)
|
||||
if(this.autoTransformBeforePut()) {
|
||||
config.data = objectToSnakeCase(config.data)
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'delete':
|
||||
if (this.useDeleteInterceptor()) {
|
||||
config.data = this.beforeDelete(config.data)
|
||||
if(this.autoTransformBeforeDelete()) {
|
||||
config.data = objectToSnakeCase(config.data)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return config
|
||||
|
@ -120,6 +126,22 @@ export default abstract class AbstractService<Model extends IAbstract = IAbstrac
|
|||
return true
|
||||
}
|
||||
|
||||
autoTransformBeforeSend(): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
autoTransformBeforePost(): boolean {
|
||||
return this.autoTransformBeforeSend()
|
||||
}
|
||||
|
||||
autoTransformBeforePut(): boolean {
|
||||
return this.autoTransformBeforeSend()
|
||||
}
|
||||
|
||||
autoTransformBeforeDelete(): boolean {
|
||||
return this.autoTransformBeforeSend()
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Helper functions
|
||||
///////////////
|
||||
|
|
32
frontend/src/services/reactions.ts
Normal file
32
frontend/src/services/reactions.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import AbstractService from '@/services/abstractService'
|
||||
import type {IAbstract} from '@/modelTypes/IAbstract'
|
||||
import ReactionModel from '@/models/reaction'
|
||||
import type {IReactionPerEntity} from '@/modelTypes/IReaction'
|
||||
import UserModel from '@/models/user'
|
||||
|
||||
export default class ReactionService extends AbstractService {
|
||||
constructor() {
|
||||
super({
|
||||
getAll: '{kind}/{id}/reactions',
|
||||
create: '{kind}/{id}/reactions',
|
||||
delete: '{kind}/{id}/reactions/delete',
|
||||
})
|
||||
}
|
||||
|
||||
modelFactory(data: Partial<IAbstract>): ReactionModel {
|
||||
return new ReactionModel(data)
|
||||
}
|
||||
|
||||
modelGetAllFactory(data: Partial<IReactionPerEntity>): Partial<IReactionPerEntity> {
|
||||
Object.keys(data).forEach(reaction => {
|
||||
data[reaction] = data[reaction]?.map(u => new UserModel(u))
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
async delete(model: IAbstract) {
|
||||
const finalUrl = this.getReplacedRoute(this.paths.delete, model)
|
||||
return super.post(finalUrl, model)
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import LabelService from './label'
|
|||
|
||||
import {colorFromHex} from '@/helpers/color/colorFromHex'
|
||||
import {SECONDS_A_DAY, SECONDS_A_HOUR, SECONDS_A_WEEK} from '@/constants/date'
|
||||
import {objectToSnakeCase} from '@/helpers/case'
|
||||
|
||||
const parseDate = date => {
|
||||
if (date) {
|
||||
|
@ -38,6 +39,10 @@ export default class TaskService extends AbstractService<ITask> {
|
|||
return this.processModel(model)
|
||||
}
|
||||
|
||||
autoTransformBeforePost(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
processModel(updatedModel) {
|
||||
const model = {...updatedModel}
|
||||
|
||||
|
@ -108,7 +113,15 @@ export default class TaskService extends AbstractService<ITask> {
|
|||
model.labels = model.labels.map(l => labelService.processModel(l))
|
||||
}
|
||||
|
||||
return model as ITask
|
||||
const transformed = objectToSnakeCase(model)
|
||||
|
||||
// We can't convert emojis to skane case, hence we add them back again
|
||||
transformed.reactions = {}
|
||||
Object.keys(updatedModel.reactions || {}).forEach(reaction => {
|
||||
transformed.reactions[reaction] = updatedModel.reactions[reaction].map(u => objectToSnakeCase(u))
|
||||
})
|
||||
|
||||
return transformed as ITask
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,19 +4,22 @@ import TaskModel from '@/models/task'
|
|||
import type {ITask} from '@/modelTypes/ITask'
|
||||
|
||||
export interface TaskFilterParams {
|
||||
sort_by: ('start_date' | 'done' | 'id' | 'position')[],
|
||||
sort_by: ('start_date' | 'end_date' | 'due_date' | 'done' | 'id' | 'position' | 'kanban_position')[],
|
||||
order_by: ('asc' | 'desc')[],
|
||||
filter: string,
|
||||
filter_include_nulls: boolean,
|
||||
filter_timezone?: string,
|
||||
s: string,
|
||||
per_page?: number,
|
||||
}
|
||||
|
||||
export function getDefaultTaskFilterParams(): TaskFilterParams {
|
||||
return {
|
||||
sort_by: ['position', 'id'],
|
||||
order_by: ['asc', 'desc'],
|
||||
filter: '',
|
||||
filter: 'done = false',
|
||||
filter_include_nulls: false,
|
||||
filter_timezone: '',
|
||||
s: '',
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import AbstractService from './abstractService'
|
||||
import TaskCommentModel from '@/models/taskComment'
|
||||
import type {ITaskComment} from '@/modelTypes/ITaskComment'
|
||||
import {objectToSnakeCase} from '@/helpers/case'
|
||||
|
||||
export default class TaskCommentService extends AbstractService<ITaskComment> {
|
||||
constructor() {
|
||||
|
@ -16,4 +17,22 @@ export default class TaskCommentService extends AbstractService<ITaskComment> {
|
|||
modelFactory(data) {
|
||||
return new TaskCommentModel(data)
|
||||
}
|
||||
|
||||
autoTransformBeforePost(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
beforeUpdate(model: ITaskComment) {
|
||||
const transformed = objectToSnakeCase({...model})
|
||||
|
||||
// We can't convert emojis to skane case, hence we add them back again
|
||||
transformed.reactions = {}
|
||||
Object.keys(model.reactions || {}).forEach(reaction => {
|
||||
transformed.reactions[reaction] = model.reactions[reaction].map(u => objectToSnakeCase(u))
|
||||
})
|
||||
|
||||
console.log()
|
||||
|
||||
return transformed as ITaskComment
|
||||
}
|
||||
}
|
|
@ -37,6 +37,7 @@ export interface ConfigState {
|
|||
providers: IProvider[],
|
||||
},
|
||||
},
|
||||
publicTeamsEnabled: boolean,
|
||||
}
|
||||
|
||||
export const useConfigStore = defineStore('config', () => {
|
||||
|
@ -70,6 +71,7 @@ export const useConfigStore = defineStore('config', () => {
|
|||
providers: [],
|
||||
},
|
||||
},
|
||||
publicTeamsEnabled: false,
|
||||
})
|
||||
|
||||
const migratorsEnabled = computed(() => state.availableMigrators?.length > 0)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {computed, readonly, ref} from 'vue'
|
||||
import {defineStore, acceptHMRUpdate} from 'pinia'
|
||||
import {acceptHMRUpdate, defineStore} from 'pinia'
|
||||
import {klona} from 'klona/lite'
|
||||
|
||||
import {findById, findIndexById} from '@/helpers/utils'
|
||||
|
@ -7,13 +7,14 @@ import {i18n} from '@/i18n'
|
|||
import {success} from '@/message'
|
||||
|
||||
import BucketService from '@/services/bucket'
|
||||
import TaskCollectionService from '@/services/taskCollection'
|
||||
import TaskCollectionService, {type TaskFilterParams} from '@/services/taskCollection'
|
||||
|
||||
import {setModuleLoading} from '@/stores/helper'
|
||||
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
import type {IBucket} from '@/modelTypes/IBucket'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
|
||||
const TASKS_PER_BUCKET = 25
|
||||
|
||||
|
@ -44,6 +45,8 @@ const addTaskToBucketAndSort = (buckets: IBucket[], task: ITask) => {
|
|||
* It should hold only the current buckets.
|
||||
*/
|
||||
export const useKanbanStore = defineStore('kanban', () => {
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const buckets = ref<IBucket[]>([])
|
||||
const projectId = ref<IProject['id']>(0)
|
||||
const bucketLoading = ref<{ [id: IBucket['id']]: boolean }>({})
|
||||
|
@ -94,13 +97,10 @@ export const useKanbanStore = defineStore('kanban', () => {
|
|||
buckets.value[bucketIndex] = newBucket
|
||||
}
|
||||
|
||||
function setBucketByIndex({
|
||||
bucketIndex,
|
||||
bucket,
|
||||
} : {
|
||||
function setBucketByIndex(
|
||||
bucketIndex: number,
|
||||
bucket: IBucket
|
||||
}) {
|
||||
bucket: IBucket,
|
||||
) {
|
||||
buckets.value[bucketIndex] = bucket
|
||||
}
|
||||
|
||||
|
@ -247,7 +247,7 @@ export const useKanbanStore = defineStore('kanban', () => {
|
|||
|
||||
async function loadNextTasksForBucket(
|
||||
projectId: IProject['id'],
|
||||
ps,
|
||||
ps: TaskFilterParams,
|
||||
bucketId: IBucket['id'],
|
||||
) {
|
||||
const isLoading = bucketLoading.value[bucketId] ?? false
|
||||
|
@ -265,28 +265,12 @@ export const useKanbanStore = defineStore('kanban', () => {
|
|||
const cancel = setModuleLoading(setIsLoading)
|
||||
setBucketLoading({bucketId: bucketId, loading: true})
|
||||
|
||||
const params = JSON.parse(JSON.stringify(ps))
|
||||
|
||||
params.sort_by = 'kanban_position'
|
||||
params.order_by = 'asc'
|
||||
|
||||
let hasBucketFilter = false
|
||||
for (const f in params.filter_by) {
|
||||
if (params.filter_by[f] === 'bucket_id') {
|
||||
hasBucketFilter = true
|
||||
if (params.filter_value[f] !== bucketId) {
|
||||
params.filter_value[f] = bucketId
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasBucketFilter) {
|
||||
params.filter_by = [...(params.filter_by ?? []), 'bucket_id']
|
||||
params.filter_value = [...(params.filter_value ?? []), bucketId]
|
||||
params.filter_comparator = [...(params.filter_comparator ?? []), 'equals']
|
||||
}
|
||||
const params: TaskFilterParams = JSON.parse(JSON.stringify(ps))
|
||||
|
||||
params.sort_by = ['kanban_position']
|
||||
params.order_by = ['asc']
|
||||
params.filter = `${params.filter === '' ? '' : params.filter + ' && '}bucket_id = ${bucketId}`
|
||||
params.filter_timezone = authStore.settings.timezone
|
||||
params.per_page = TASKS_PER_BUCKET
|
||||
|
||||
const taskService = new TaskCollectionService()
|
||||
|
@ -343,16 +327,16 @@ export const useKanbanStore = defineStore('kanban', () => {
|
|||
...updatedBucketData,
|
||||
}
|
||||
|
||||
setBucketByIndex({bucketIndex, bucket: updatedBucket})
|
||||
setBucketByIndex(bucketIndex, updatedBucket)
|
||||
|
||||
const bucketService = new BucketService()
|
||||
try {
|
||||
const returnedBucket = await bucketService.update(updatedBucket)
|
||||
setBucketByIndex({bucketIndex, bucket: returnedBucket})
|
||||
setBucketByIndex(bucketIndex, returnedBucket)
|
||||
return returnedBucket
|
||||
} catch (e) {
|
||||
// restore original state
|
||||
setBucketByIndex({bucketIndex, bucket: oldBucket})
|
||||
setBucketByIndex(bucketIndex, oldBucket)
|
||||
|
||||
throw e
|
||||
} finally {
|
||||
|
|
|
@ -58,6 +58,12 @@ export const useLabelStore = defineStore('label', () => {
|
|||
.filter(({title}) => labelTitles.some(l => l.toLowerCase() === title.toLowerCase()))
|
||||
})
|
||||
|
||||
const getLabelByExactTitle = computed(() => {
|
||||
return (labelTitle: string) => Object
|
||||
.values(labels.value)
|
||||
.find(l => l.title.toLowerCase() === labelTitle.toLowerCase())
|
||||
})
|
||||
|
||||
|
||||
function setIsLoading(newIsLoading: boolean) {
|
||||
isLoading.value = newIsLoading
|
||||
|
@ -145,6 +151,7 @@ export const useLabelStore = defineStore('label', () => {
|
|||
getLabelById,
|
||||
filterLabelsByQuery,
|
||||
getLabelsByExactTitles,
|
||||
getLabelByExactTitle,
|
||||
|
||||
setLabels,
|
||||
setLabel,
|
||||
|
|
|
@ -28,7 +28,7 @@ import {useKanbanStore} from '@/stores/kanban'
|
|||
import {useBaseStore} from '@/stores/base'
|
||||
import ProjectUserService from '@/services/projectUsers'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import TaskCollectionService from '@/services/taskCollection'
|
||||
import TaskCollectionService, {type TaskFilterParams} from '@/services/taskCollection'
|
||||
import {getRandomColorHex} from '@/helpers/color/randomColor'
|
||||
|
||||
interface MatchedAssignee extends IUser {
|
||||
|
@ -124,7 +124,11 @@ export const useTaskStore = defineStore('task', () => {
|
|||
})
|
||||
}
|
||||
|
||||
async function loadTasks(params, projectId: IProject['id'] | null = null) {
|
||||
async function loadTasks(params: TaskFilterParams, projectId: IProject['id'] | null = null) {
|
||||
|
||||
if (!params.filter_timezone || params.filter_timezone === '') {
|
||||
params.filter_timezone = authStore.settings.timezone
|
||||
}
|
||||
|
||||
const cancel = setModuleLoading(setIsLoading)
|
||||
try {
|
||||
|
|
|
@ -18,14 +18,12 @@ $filter-container-top-link-share-list: -47px;
|
|||
margin-top: $filter-container-top-default;
|
||||
z-index: 4;
|
||||
|
||||
.items {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.button:not(:last-of-type) {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
height: $switch-view-height;
|
||||
|
@ -35,37 +33,6 @@ $filter-container-top-link-share-list: -47px;
|
|||
text-align: left;
|
||||
}
|
||||
|
||||
.fancycheckbox {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-right: .5rem;
|
||||
|
||||
.field {
|
||||
transition: width $transition;
|
||||
width: 100%;
|
||||
|
||||
&.hidden {
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.button {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filters input {
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
position: static;
|
||||
margin: 0 0 1rem 0 !important;
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
:class="{ 'disabled': filterService.loading}"
|
||||
:disabled="filterService.loading"
|
||||
class="has-no-shadow has-no-border"
|
||||
:has-footer="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,13 +5,11 @@
|
|||
view-name="kanban"
|
||||
>
|
||||
<template #header>
|
||||
<div
|
||||
<div class="filter-container">
|
||||
<FilterPopup
|
||||
v-if="!isSavedFilter(project)"
|
||||
class="filter-container"
|
||||
>
|
||||
<div class="items">
|
||||
<FilterPopup v-model="params" />
|
||||
</div>
|
||||
v-model="params"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -197,7 +195,9 @@
|
|||
variant="secondary"
|
||||
@click="toggleShowNewTaskInput(bucket.id)"
|
||||
>
|
||||
{{ bucket.tasks.length === 0 ? $t('project.kanban.addTask') : $t('project.kanban.addAnotherTask') }}
|
||||
{{
|
||||
bucket.tasks.length === 0 ? $t('project.kanban.addTask') : $t('project.kanban.addAnotherTask')
|
||||
}}
|
||||
</x-button>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -290,7 +290,11 @@ import KanbanCard from '@/components/tasks/partials/kanban-card.vue'
|
|||
import Dropdown from '@/components/misc/dropdown.vue'
|
||||
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||
|
||||
import {getCollapsedBucketState, saveCollapsedBucketState, type CollapsedBuckets} from '@/helpers/saveCollapsedBucketState'
|
||||
import {
|
||||
type CollapsedBuckets,
|
||||
getCollapsedBucketState,
|
||||
saveCollapsedBucketState,
|
||||
} from '@/helpers/saveCollapsedBucketState'
|
||||
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
|
||||
|
||||
import {isSavedFilter} from '@/services/savedFilter'
|
||||
|
@ -554,7 +558,6 @@ async function createNewBucket() {
|
|||
projectId: project.value.id,
|
||||
}))
|
||||
newBucketTitle.value = ''
|
||||
showNewBucketInput.value = false
|
||||
}
|
||||
|
||||
function deleteBucketModal(bucketId: IBucket['id']) {
|
||||
|
@ -740,6 +743,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
|||
* {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
@ -781,6 +785,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
|||
&:first-of-type {
|
||||
padding-top: .5rem;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
padding-bottom: .5rem;
|
||||
}
|
||||
|
|
|
@ -5,53 +5,13 @@
|
|||
view-name="project"
|
||||
>
|
||||
<template #header>
|
||||
<div
|
||||
v-if="!isSavedFilter(project)"
|
||||
class="filter-container"
|
||||
>
|
||||
<div class="items">
|
||||
<div class="search">
|
||||
<div
|
||||
:class="{ hidden: !showTaskSearch }"
|
||||
class="field has-addons"
|
||||
>
|
||||
<div class="control has-icons-left has-icons-right">
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
v-focus
|
||||
class="input"
|
||||
:placeholder="$t('misc.search')"
|
||||
type="text"
|
||||
@blur="hideSearchBar()"
|
||||
@keyup.enter="searchTasks"
|
||||
>
|
||||
<span class="icon is-left">
|
||||
<icon icon="search" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
:loading="loading"
|
||||
:shadow="false"
|
||||
@click="searchTasks"
|
||||
>
|
||||
{{ $t('misc.search') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
<x-button
|
||||
v-if="!showTaskSearch"
|
||||
icon="search"
|
||||
variant="secondary"
|
||||
@click="showTaskSearch = !showTaskSearch"
|
||||
/>
|
||||
</div>
|
||||
<div class="filter-container">
|
||||
<FilterPopup
|
||||
v-if="!isSavedFilter(project)"
|
||||
v-model="params"
|
||||
@update:modelValue="prepareFiltersAndLoadTasks()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
|
@ -137,7 +97,6 @@ export default { name: 'List' }
|
|||
<script setup lang="ts">
|
||||
import {ref, computed, nextTick, onMounted, watch} from 'vue'
|
||||
import draggable from 'zhyswan-vuedraggable'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
|
||||
import ProjectWrapper from '@/components/project/ProjectWrapper.vue'
|
||||
import ButtonLink from '@/components/misc/ButtonLink.vue'
|
||||
|
@ -166,7 +125,6 @@ const {
|
|||
}>()
|
||||
|
||||
const ctaVisible = ref(false)
|
||||
const showTaskSearch = ref(false)
|
||||
|
||||
const drag = ref(false)
|
||||
const DRAG_OPTIONS = {
|
||||
|
@ -180,7 +138,6 @@ const {
|
|||
totalPages,
|
||||
currentPage,
|
||||
loadTasks,
|
||||
searchTerm,
|
||||
params,
|
||||
sortByParam,
|
||||
} = useTaskList(() => projectId, {position: 'asc'})
|
||||
|
@ -238,33 +195,8 @@ onMounted(async () => {
|
|||
ctaVisible.value = true
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
function searchTasks() {
|
||||
// Only search if the search term changed
|
||||
if (route.query as unknown as string === searchTerm.value) {
|
||||
return
|
||||
}
|
||||
|
||||
router.push({
|
||||
name: 'project.list',
|
||||
query: {search: searchTerm.value},
|
||||
})
|
||||
}
|
||||
|
||||
function hideSearchBar() {
|
||||
// This is a workaround.
|
||||
// When clicking on the search button, @blur from the input is fired. If we
|
||||
// would then directly hide the whole search bar directly, no click event
|
||||
// from the button gets fired. To prevent this, we wait 200ms until we hide
|
||||
// everything so the button has a chance of firing the search event.
|
||||
setTimeout(() => {
|
||||
showTaskSearch.value = false
|
||||
}, 200)
|
||||
}
|
||||
|
||||
const addTaskRef = ref<typeof AddTask | null>(null)
|
||||
|
||||
function focusNewTaskInput() {
|
||||
addTaskRef.value?.focusTaskInput()
|
||||
}
|
||||
|
@ -273,8 +205,7 @@ function updateTaskList(task: ITask) {
|
|||
if (isAlphabeticalSorting.value) {
|
||||
// reload tasks with current filter and sorting
|
||||
loadTasks()
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
allTasks.value = [
|
||||
task,
|
||||
...allTasks.value,
|
||||
|
|
|
@ -284,9 +284,8 @@ import FilterPopup from '@/components/project/partials/filter-popup.vue'
|
|||
import Pagination from '@/components/misc/pagination.vue'
|
||||
import Popup from '@/components/misc/popup.vue'
|
||||
|
||||
import {useTaskList} from '@/composables/useTaskList'
|
||||
|
||||
import type {SortBy} from '@/composables/useTaskList'
|
||||
import {useTaskList} from '@/composables/useTaskList'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
import AssigneeList from '@/components/tasks/partials/assigneeList.vue'
|
||||
|
@ -333,9 +332,7 @@ const {
|
|||
const tasks: Ref<ITask[]> = taskList.tasks
|
||||
|
||||
Object.assign(params.value, {
|
||||
filter_by: [],
|
||||
filter_value: [],
|
||||
filter_comparator: [],
|
||||
filter: '',
|
||||
})
|
||||
|
||||
// FIXME: by doing this we can have multiple sort orders
|
||||
|
@ -383,6 +380,11 @@ const taskDetailRoutes = computed(() => Object.fromEntries(
|
|||
.columns-filter {
|
||||
margin: 0;
|
||||
|
||||
:deep(.card-content .content) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.is-open {
|
||||
margin: 2rem 0 1rem;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import TaskService from '@/services/task'
|
|||
|
||||
import TaskModel from '@/models/task'
|
||||
import {error, success} from '@/message'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
|
||||
// FIXME: unify with general `useTaskList`
|
||||
export function useGanttTaskList<F extends Filters>(
|
||||
|
@ -21,12 +22,18 @@ export function useGanttTaskList<F extends Filters>(
|
|||
}) {
|
||||
const taskCollectionService = shallowReactive(new TaskCollectionService())
|
||||
const taskService = shallowReactive(new TaskService())
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const isLoading = computed(() => taskCollectionService.loading)
|
||||
|
||||
const tasks = ref<Map<ITask['id'], ITask>>(new Map())
|
||||
|
||||
async function fetchTasks(params: TaskFilterParams, page = 1): Promise<ITask[]> {
|
||||
|
||||
if(params.filter_timezone === '') {
|
||||
params.filter_timezone = authStore.settings.timezone
|
||||
}
|
||||
|
||||
const tasks = await taskCollectionService.getAll({projectId: filters.value.projectId}, params, page) as ITask[]
|
||||
if (options.loadAll && page < taskCollectionService.totalPages) {
|
||||
const nextTasks = await fetchTasks(params, page + 1)
|
||||
|
|
|
@ -16,9 +16,7 @@
|
|||
v-if="totalTasks !== null"
|
||||
class="has-text-weight-bold"
|
||||
>
|
||||
{{
|
||||
totalTasks > 0 ? $t('project.delete.tasksToDelete', {count: totalTasks}) : $t('project.delete.noTasksToDelete')
|
||||
}}
|
||||
{{ deleteNotice }}
|
||||
</p>
|
||||
<Loading
|
||||
v-else
|
||||
|
@ -39,9 +37,9 @@ import {useTitle} from '@/composables/useTitle'
|
|||
import {useI18n} from 'vue-i18n'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import {success} from '@/message'
|
||||
import TaskCollectionService from '@/services/taskCollection'
|
||||
import Loading from '@/components/misc/loading.vue'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import TaskService from '@/services/task'
|
||||
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
const projectStore = useProjectStore()
|
||||
|
@ -51,6 +49,7 @@ const router = useRouter()
|
|||
const totalTasks = ref<number | null>(null)
|
||||
|
||||
const project = computed(() => projectStore.projects[route.params.projectId])
|
||||
const childProjectIds = ref<number[]>([])
|
||||
|
||||
watchEffect(
|
||||
() => {
|
||||
|
@ -58,15 +57,32 @@ watchEffect(
|
|||
return
|
||||
}
|
||||
|
||||
const taskCollectionService = new TaskCollectionService()
|
||||
taskCollectionService.getAll({projectId: route.params.projectId}).then(() => {
|
||||
totalTasks.value = taskCollectionService.totalPages * taskCollectionService.resultCount
|
||||
childProjectIds.value = projectStore.getChildProjects(parseInt(route.params.projectId)).map(p => p.id)
|
||||
if (childProjectIds.value.length === 0) {
|
||||
childProjectIds.value = [parseInt(route.params.projectId)]
|
||||
}
|
||||
|
||||
const taskService = new TaskService()
|
||||
taskService.getAll({}, {filter: `project in ${childProjectIds.value.join(',')}`}).then(() => {
|
||||
totalTasks.value = taskService.totalPages * taskService.resultCount
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
useTitle(() => t('project.delete.title', {project: project?.value?.title}))
|
||||
|
||||
const deleteNotice = computed(() => {
|
||||
if(totalTasks.value && totalTasks.value > 0 && childProjectIds.value.length <= 1) {
|
||||
return t('project.delete.tasksToDelete', {count: totalTasks.value})
|
||||
}
|
||||
|
||||
if(totalTasks.value && totalTasks.value > 0 && childProjectIds.value.length > 1) {
|
||||
return t('project.delete.tasksAndChildProjectsToDelete', {tasks: totalTasks.value, projects: childProjectIds.value.length})
|
||||
}
|
||||
|
||||
return t('project.delete.noTasksToDelete')
|
||||
})
|
||||
|
||||
async function deleteProject() {
|
||||
if (!project.value) {
|
||||
return
|
||||
|
|
|
@ -85,6 +85,7 @@ import type {ITask} from '@/modelTypes/ITask'
|
|||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useTaskStore} from '@/stores/tasks'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import type {TaskFilterParams} from '@/services/taskCollection'
|
||||
|
||||
// Linting disabled because we explicitely enabled destructuring in vite's config, this will work.
|
||||
// eslint-disable-next-line vue/no-setup-props-destructure
|
||||
|
@ -172,7 +173,7 @@ function setShowNulls(show: boolean) {
|
|||
})
|
||||
}
|
||||
|
||||
async function loadPendingTasks(from: string, to: string) {
|
||||
async function loadPendingTasks(from: Date|string, to: Date|string) {
|
||||
// FIXME: HACK! This should never happen.
|
||||
// Since this route is authentication only, users would get an error message if they access the page unauthenticated.
|
||||
// Since this component is mounted as the home page before unauthenticated users get redirected
|
||||
|
@ -181,28 +182,23 @@ async function loadPendingTasks(from: string, to: string) {
|
|||
return
|
||||
}
|
||||
|
||||
const params = {
|
||||
sortBy: ['due_date', 'id'],
|
||||
orderBy: ['asc', 'desc'],
|
||||
filterBy: ['done'],
|
||||
filterValue: ['false'],
|
||||
filterComparator: ['equals'],
|
||||
filterConcat: 'and',
|
||||
filterIncludeNulls: showNulls,
|
||||
const params: TaskFilterParams = {
|
||||
sort_by: ['due_date', 'id'],
|
||||
order_by: ['asc', 'desc'],
|
||||
filter: 'done = false',
|
||||
filter_include_nulls: showNulls,
|
||||
s: '',
|
||||
}
|
||||
|
||||
if (!showAll.value) {
|
||||
params.filterBy.push('due_date')
|
||||
params.filterValue.push(to)
|
||||
params.filterComparator.push('less')
|
||||
|
||||
params.filter += ` && due_date < '${to instanceof Date ? to.toISOString() : to}'`
|
||||
|
||||
// NOTE: Ideally we could also show tasks with a start or end date in the specified range, but the api
|
||||
// is not capable (yet) of combining multiple filters with 'and' and 'or'.
|
||||
|
||||
if (!showOverdue) {
|
||||
params.filterBy.push('due_date')
|
||||
params.filterValue.push(from)
|
||||
params.filterComparator.push('greater')
|
||||
params.filter += ` && due_date > '${from instanceof Date ? from.toISOString() : from}'`
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,16 @@
|
|||
v-for="p in projectStore.getAncestors(project)"
|
||||
:key="p.id"
|
||||
>
|
||||
<router-link :to="{ name: 'project.index', params: { projectId: p.id } }">
|
||||
<a
|
||||
v-if="router.options.history.state.back?.includes('/projects/'+p.id+'/') || false"
|
||||
@click="router.back()"
|
||||
>
|
||||
{{ getProjectTitle(p) }}
|
||||
</a>
|
||||
<router-link
|
||||
v-else
|
||||
:to="{ name: 'project.index', params: { projectId: p.id } }"
|
||||
>
|
||||
{{ getProjectTitle(p) }}
|
||||
</router-link>
|
||||
<span
|
||||
|
@ -313,6 +322,14 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<!-- Reactions -->
|
||||
<Reactions
|
||||
v-model="task.reactions"
|
||||
entity-kind="tasks"
|
||||
:entity-id="task.id"
|
||||
class="details"
|
||||
/>
|
||||
|
||||
<!-- Attachments -->
|
||||
<div
|
||||
v-if="activeFields.attachments || hasAttachments"
|
||||
|
@ -616,6 +633,7 @@ import {TASK_REPEAT_MODES} from '@/types/IRepeatMode'
|
|||
import {useAuthStore} from '@/stores/auth'
|
||||
import {playPopSound} from '@/helpers/playPop'
|
||||
import AssigneeList from '@/components/tasks/partials/assigneeList.vue'
|
||||
import Reactions from '@/components/input/Reactions.vue'
|
||||
|
||||
const {
|
||||
taskId,
|
||||
|
|
|
@ -33,6 +33,27 @@
|
|||
>
|
||||
{{ $t('team.attributes.nameRequired') }}
|
||||
</p>
|
||||
<div
|
||||
v-if="configStore.publicTeamsEnabled"
|
||||
class="field"
|
||||
>
|
||||
<label
|
||||
class="label"
|
||||
for="teamIsPublic"
|
||||
>{{ $t('team.attributes.isPublic') }}</label>
|
||||
<div
|
||||
class="control is-expanded"
|
||||
:class="{ 'is-loading': teamService.loading }"
|
||||
>
|
||||
<Fancycheckbox
|
||||
v-model="team.isPublic"
|
||||
:disabled="teamMemberService.loading || undefined"
|
||||
:class="{ 'disabled': teamService.loading }"
|
||||
>
|
||||
{{ $t('team.attributes.isPublicDescription') }}
|
||||
</Fancycheckbox>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label
|
||||
class="label"
|
||||
|
@ -242,6 +263,7 @@ import {useI18n} from 'vue-i18n'
|
|||
import {useRoute, useRouter} from 'vue-router'
|
||||
|
||||
import Editor from '@/components/input/AsyncEditor'
|
||||
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
||||
import Multiselect from '@/components/input/multiselect.vue'
|
||||
import User from '@/components/misc/user.vue'
|
||||
|
||||
|
@ -254,12 +276,14 @@ import {RIGHTS as Rights} from '@/constants/rights'
|
|||
import {useTitle} from '@/composables/useTitle'
|
||||
import {success} from '@/message'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useConfigStore} from '@/stores/config'
|
||||
|
||||
import type {ITeam} from '@/modelTypes/ITeam'
|
||||
import type {IUser} from '@/modelTypes/IUser'
|
||||
import type {ITeamMember} from '@/modelTypes/ITeamMember'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const configStore = useConfigStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
|
|
|
@ -25,6 +25,26 @@
|
|||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="configStore.publicTeamsEnabled"
|
||||
class="field"
|
||||
>
|
||||
<label
|
||||
class="label"
|
||||
for="teamIsPublic"
|
||||
>{{ $t('team.attributes.isPublic') }}</label>
|
||||
<div
|
||||
class="control is-expanded"
|
||||
:class="{ 'is-loading': teamService.loading }"
|
||||
>
|
||||
<Fancycheckbox
|
||||
v-model="team.isPublic"
|
||||
:class="{ 'disabled': teamService.loading }"
|
||||
>
|
||||
{{ $t('team.attributes.isPublicDescription') }}
|
||||
</Fancycheckbox>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
v-if="showError && team.name === ''"
|
||||
class="help is-danger"
|
||||
|
@ -46,11 +66,14 @@ import TeamModel from '@/models/team'
|
|||
import TeamService from '@/services/team'
|
||||
|
||||
import CreateEdit from '@/components/misc/create-edit.vue'
|
||||
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
||||
|
||||
import {useTitle} from '@/composables/useTitle'
|
||||
import {useRouter} from 'vue-router'
|
||||
import {success} from '@/message'
|
||||
|
||||
import {useConfigStore} from '@/stores/config'
|
||||
|
||||
const {t} = useI18n()
|
||||
const title = computed(() => t('team.create.title'))
|
||||
useTitle(title)
|
||||
|
@ -60,6 +83,8 @@ const teamService = shallowReactive(new TeamService())
|
|||
const team = reactive(new TeamModel())
|
||||
const showError = ref(false)
|
||||
|
||||
const configStore = useConfigStore()
|
||||
|
||||
async function newTeam() {
|
||||
if (team.name === '') {
|
||||
showError.value = true
|
||||
|
|
4
go.mod
4
go.mod
|
@ -179,7 +179,7 @@ require (
|
|||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
|
@ -190,3 +190,5 @@ replace github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20
|
|||
go 1.21
|
||||
|
||||
toolchain go1.21.2
|
||||
|
||||
replace github.com/adlio/trello => github.com/kolaente/trello v1.8.1-0.20240310152004-14ccae2ddc51
|
||||
|
|
8
go.sum
8
go.sum
|
@ -24,8 +24,6 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
|
|||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||
github.com/ThreeDotsLabs/watermill v1.3.5 h1:50JEPEhMGZQMh08ct0tfO1PsgMOAOhV3zxK2WofkbXg=
|
||||
github.com/ThreeDotsLabs/watermill v1.3.5/go.mod h1:O/u/Ptyrk5MPTxSeWM5vzTtZcZfxXfO9PK9eXTYiFZY=
|
||||
github.com/adlio/trello v1.10.0 h1:ia/rzoBwJJKr4IqnMlrU6n09CVqeyaahSkEVcV5/gPc=
|
||||
github.com/adlio/trello v1.10.0/go.mod h1:I4Lti4jf2KxjTNgTqs5W3lLuE78QZZdYbbPnQQGwjOo=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
|
@ -301,6 +299,8 @@ github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZY
|
|||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible h1:q7DbyV+sFjEoTuuUdRDNl2nlyfztkZgxVVCV7JhzIkY=
|
||||
github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw=
|
||||
github.com/kolaente/trello v1.8.1-0.20240310152004-14ccae2ddc51 h1:R8xiJ/zSWOndiUjG03GmkkIm1O8MDKt2av0SeaIZy/c=
|
||||
github.com/kolaente/trello v1.8.1-0.20240310152004-14ccae2ddc51/go.mod h1:I4Lti4jf2KxjTNgTqs5W3lLuE78QZZdYbbPnQQGwjOo=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
|
@ -705,8 +705,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
@ -64,6 +64,7 @@ const (
|
|||
ServiceMaxAvatarSize Key = `service.maxavatarsize`
|
||||
ServiceAllowIconChanges Key = `service.allowiconchanges`
|
||||
ServiceCustomLogoURL Key = `service.customlogourl`
|
||||
ServiceEnablePublicTeams Key = `service.enablepublicteams`
|
||||
|
||||
SentryEnabled Key = `sentry.enabled`
|
||||
SentryDsn Key = `sentry.dsn`
|
||||
|
@ -312,6 +313,7 @@ func InitDefaultConfig() {
|
|||
ServiceMaxAvatarSize.setDefault(1024)
|
||||
ServiceDemoMode.setDefault(false)
|
||||
ServiceAllowIconChanges.setDefault(true)
|
||||
ServiceEnablePublicTeams.setDefault(false)
|
||||
|
||||
// Sentry
|
||||
SentryDsn.setDefault("https://440eedc957d545a795c17bbaf477497c@o1047380.ingest.sentry.io/4504254983634944")
|
||||
|
|
11
pkg/db/db.go
11
pkg/db/db.go
|
@ -26,6 +26,8 @@ import (
|
|||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/names"
|
||||
"xorm.io/xorm/schemas"
|
||||
|
@ -227,3 +229,12 @@ func NewSession() *xorm.Session {
|
|||
func Type() schemas.DBType {
|
||||
return x.Dialect().URI().DBType
|
||||
}
|
||||
|
||||
func GetDialect() string {
|
||||
dialect := config.DatabaseType.GetString()
|
||||
if dialect == "sqlite" {
|
||||
dialect = builder.SQLITE
|
||||
}
|
||||
|
||||
return dialect
|
||||
}
|
||||
|
|
6
pkg/db/fixtures/reactions.yml
Normal file
6
pkg/db/fixtures/reactions.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
- id: 1
|
||||
user_id: 1
|
||||
entity_id: 1
|
||||
entity_kind: 0
|
||||
value: '👋'
|
||||
created: 2024-03-12 15:00:00
|
|
@ -100,3 +100,9 @@
|
|||
task_id: 35
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 18
|
||||
comment: comment 18
|
||||
author_id: 13
|
||||
task_id: 34
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
|
|
|
@ -1,61 +1,49 @@
|
|||
-
|
||||
team_id: 1
|
||||
- team_id: 1
|
||||
user_id: 1
|
||||
admin: true
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 1
|
||||
- team_id: 1
|
||||
user_id: 2
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 2
|
||||
- team_id: 2
|
||||
user_id: 1
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 3
|
||||
- team_id: 3
|
||||
user_id: 1
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 4
|
||||
- team_id: 4
|
||||
user_id: 1
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 5
|
||||
- team_id: 5
|
||||
user_id: 1
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 6
|
||||
- team_id: 6
|
||||
user_id: 1
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 7
|
||||
- team_id: 7
|
||||
user_id: 1
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 8
|
||||
- team_id: 8
|
||||
user_id: 1
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 9
|
||||
- team_id: 9
|
||||
user_id: 2
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 10
|
||||
- team_id: 10
|
||||
user_id: 3
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 11
|
||||
- team_id: 11
|
||||
user_id: 8
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 12
|
||||
- team_id: 12
|
||||
user_id: 9
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 13
|
||||
- team_id: 13
|
||||
user_id: 10
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 14
|
||||
- team_id: 14
|
||||
user_id: 10
|
||||
created: 2018-12-01 15:13:12
|
||||
- team_id: 15
|
||||
user_id: 10
|
||||
created: 2018-12-01 15:13:12
|
|
@ -29,8 +29,16 @@
|
|||
- id: 13
|
||||
name: testteam13
|
||||
created_by_id: 7
|
||||
is_public: true
|
||||
- id: 14
|
||||
name: testteam14
|
||||
created_by_id: 7
|
||||
oidc_id: 14
|
||||
issuer: "https://some.issuer"
|
||||
- id: 15
|
||||
name: testteam15
|
||||
created_by_id: 7
|
||||
oidc_id: 15
|
||||
issuer: "https://some.issuer"
|
||||
is_public: true
|
||||
description: "This is a public team"
|
||||
|
|
|
@ -95,6 +95,7 @@ func FullInit() {
|
|||
models.RegisterUserDeletionCron()
|
||||
models.RegisterOldExportCleanupCron()
|
||||
openid.CleanupSavedOpenIDProviders()
|
||||
openid.RegisterEmptyOpenIDTeamCleanupCron()
|
||||
|
||||
// Start processing events
|
||||
go func() {
|
||||
|
|
|
@ -115,49 +115,49 @@ func TestTaskCollection(t *testing.T) {
|
|||
t.Run("by priority", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
})
|
||||
t.Run("by priority desc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
|
||||
})
|
||||
t.Run("by priority asc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
})
|
||||
// should equal duedate asc
|
||||
t.Run("by due_date", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
})
|
||||
t.Run("by duedate desc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
})
|
||||
// Due date without unix suffix
|
||||
t.Run("by duedate asc without suffix", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
})
|
||||
t.Run("by due_date without suffix", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
})
|
||||
t.Run("by duedate desc without suffix", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
})
|
||||
t.Run("by duedate asc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
})
|
||||
t.Run("invalid sort parameter", func(t *testing.T) {
|
||||
_, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams)
|
||||
|
@ -358,33 +358,33 @@ func TestTaskCollection(t *testing.T) {
|
|||
t.Run("by priority", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":35,"title":"task #35","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":21,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":[{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"labels":[{"id":4,"title":"Label #4 - visible via other task","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"hex_color":"","percent_done":0,"identifier":"test21-1","index":1,"related_tasks":{"related":[{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null},{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null}]},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":19,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":35,"title":"task #35","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":21,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":[{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"labels":[{"id":4,"title":"Label #4 - visible via other task","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"hex_color":"","percent_done":0,"identifier":"test21-1","index":1,"related_tasks":{"related":[{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"reactions":null,"created_by":null},{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"reactions":null,"created_by":null}]},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":19,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
})
|
||||
t.Run("by priority desc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
|
||||
})
|
||||
t.Run("by priority asc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":35,"title":"task #35","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":21,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":[{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"labels":[{"id":4,"title":"Label #4 - visible via other task","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"hex_color":"","percent_done":0,"identifier":"test21-1","index":1,"related_tasks":{"related":[{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null},{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null}]},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":19,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":35,"title":"task #35","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":21,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":[{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"labels":[{"id":4,"title":"Label #4 - visible via other task","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"hex_color":"","percent_done":0,"identifier":"test21-1","index":1,"related_tasks":{"related":[{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"reactions":null,"created_by":null},{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"reactions":null,"created_by":null}]},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":19,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
})
|
||||
// should equal duedate asc
|
||||
t.Run("by due_date", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
})
|
||||
t.Run("by duedate desc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
})
|
||||
t.Run("by duedate asc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
})
|
||||
t.Run("invalid parameter", func(t *testing.T) {
|
||||
// Invalid parameter should not sort at all
|
||||
|
|
43
pkg/migration/20240309111148.go
Normal file
43
pkg/migration/20240309111148.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
// 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 teams20240309111148 struct {
|
||||
IsPublic bool `xorm:"not null default false" json:"is_public"`
|
||||
}
|
||||
|
||||
func (teams20240309111148) TableName() string {
|
||||
return "teams"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20240309111148",
|
||||
Description: "Add IsPublic field to teams table to control discoverability of teams.",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(teams20240309111148{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
50
pkg/migration/20240311173251.go
Normal file
50
pkg/migration/20240311173251.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
// 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 (
|
||||
"time"
|
||||
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type reactions20240311173251 struct {
|
||||
ID int64 `xorm:"autoincr not null unique pk" json:"id" param:"reaction"`
|
||||
UserID int64 `xorm:"bigint not null INDEX" json:"-"`
|
||||
EntityID int64 `xorm:"bigint not null INDEX" json:"entity_id"`
|
||||
EntityKind int `xorm:"bigint not null INDEX" json:"entity_kind"`
|
||||
Value string `xorm:"varchar(20) not null INDEX" json:"value"`
|
||||
Created time.Time `xorm:"created not null" json:"created"`
|
||||
}
|
||||
|
||||
func (reactions20240311173251) TableName() string {
|
||||
return "reactions"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20240311173251",
|
||||
Description: "Create reactions table",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(reactions20240311173251{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
|
@ -1060,6 +1060,33 @@ func (err ErrInvalidFilterExpression) HTTPError() web.HTTPError {
|
|||
}
|
||||
}
|
||||
|
||||
// ErrInvalidReactionEntityKind represents an error where the reaction kind is invalid
|
||||
type ErrInvalidReactionEntityKind struct {
|
||||
Kind string
|
||||
}
|
||||
|
||||
// IsErrInvalidReactionEntityKind checks if an error is ErrInvalidReactionEntityKind.
|
||||
func IsErrInvalidReactionEntityKind(err error) bool {
|
||||
_, ok := err.(ErrInvalidReactionEntityKind)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrInvalidReactionEntityKind) Error() string {
|
||||
return fmt.Sprintf("Reaction kind %s is invalid", err.Kind)
|
||||
}
|
||||
|
||||
// ErrCodeInvalidReactionEntityKind holds the unique world-error code of this error
|
||||
const ErrCodeInvalidReactionEntityKind = 4025
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrInvalidReactionEntityKind) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusBadRequest,
|
||||
Code: ErrCodeInvalidReactionEntityKind,
|
||||
Message: fmt.Sprintf("The reaction kind '%s' is invalid.", err.Kind),
|
||||
}
|
||||
}
|
||||
|
||||
// ============
|
||||
// Team errors
|
||||
// ============
|
||||
|
|
|
@ -109,6 +109,7 @@ func getDefaultBucketID(s *xorm.Session, project *Project) (bucketID int64, err
|
|||
// @Param per_page query int false "The maximum number of tasks per bucket per page. This parameter is limited by the configured maximum of items per page."
|
||||
// @Param s query string false "Search tasks by task text."
|
||||
// @Param filter query string false "The filter query to match tasks by. Check out https://vikunja.io/docs/filters for a full explanation of the feature."
|
||||
// @Param filter_timezone query string false "The time zone which should be used for date match (statements like "now" resolve to different actual times)"
|
||||
// @Param filter_include_nulls query string false "If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`."
|
||||
// @Success 200 {array} models.Bucket "The buckets with their tasks"
|
||||
// @Failure 500 {object} models.Message "Internal server error"
|
||||
|
@ -197,7 +198,7 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int
|
|||
} else {
|
||||
filterString = "(" + originalFilter + ") && bucket_id = " + strconv.FormatInt(id, 10)
|
||||
}
|
||||
opts.parsedFilters, err = getTaskFiltersFromFilterString(filterString)
|
||||
opts.parsedFilters, err = getTaskFiltersFromFilterString(filterString, opts.filterTimezone)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -309,7 +310,7 @@ func (b *Bucket) Update(s *xorm.Session, _ web.Auth) (err error) {
|
|||
// @Failure 404 {object} web.HTTPError "The bucket does not exist."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /projects/{projectID}/buckets/{bucketID} [delete]
|
||||
func (b *Bucket) Delete(s *xorm.Session, _ web.Auth) (err error) {
|
||||
func (b *Bucket) Delete(s *xorm.Session, a web.Auth) (err error) {
|
||||
|
||||
// Prevent removing the last bucket
|
||||
total, err := s.Where("project_id = ?", b.ProjectID).Count(&Bucket{})
|
||||
|
@ -323,17 +324,27 @@ func (b *Bucket) Delete(s *xorm.Session, _ web.Auth) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Remove the bucket itself
|
||||
_, err = s.Where("id = ?", b.ID).Delete(&Bucket{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the default bucket
|
||||
p, err := GetProjectSimpleByID(s, b.ProjectID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var updateProject bool
|
||||
if b.ID == p.DefaultBucketID {
|
||||
p.DefaultBucketID = 0
|
||||
updateProject = true
|
||||
}
|
||||
if b.ID == p.DoneBucketID {
|
||||
p.DoneBucketID = 0
|
||||
updateProject = true
|
||||
}
|
||||
if updateProject {
|
||||
err = p.Update(s, a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
defaultBucketID, err := getDefaultBucketID(s, p)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -344,5 +355,11 @@ func (b *Bucket) Delete(s *xorm.Session, _ web.Auth) (err error) {
|
|||
Where("bucket_id = ?", b.ID).
|
||||
Cols("bucket_id").
|
||||
Update(&Task{BucketID: defaultBucketID})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Remove the bucket itself
|
||||
_, err = s.Where("id = ?", b.ID).Delete(&Bucket{})
|
||||
return
|
||||
}
|
||||
|
|
|
@ -197,6 +197,23 @@ func TestBucket_Delete(t *testing.T) {
|
|||
"project_id": 18,
|
||||
}, false)
|
||||
})
|
||||
t.Run("done bucket should be reset", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
b := &Bucket{
|
||||
ID: 3,
|
||||
ProjectID: 1,
|
||||
}
|
||||
err := b.Delete(s, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
db.AssertMissing(t, "projects", map[string]interface{}{
|
||||
"id": 1,
|
||||
"done_bucket_id": 3,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBucket_Update(t *testing.T) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user