Compare commits
47 Commits
71819697f2
...
41ccaea78b
Author | SHA1 | Date |
---|---|---|
renovate | 41ccaea78b | |
renovate | c5696f3e2a | |
renovate | 898707664c | |
renovate | d0b5bef68a | |
renovate | e395d4efdb | |
renovate | ce54132868 | |
renovate | 07d4d1e537 | |
renovate | a701b0452e | |
renovate | af65efcd27 | |
renovate | dc2afb9e8d | |
WofWca | e123d4f825 | |
renovate | b72c963256 | |
renovate | 149bbf17eb | |
renovate | 265d60cf42 | |
renovate | 23c9f51e73 | |
renovate | ff697d0c7a | |
renovate | 00588cf59f | |
renovate | 01089f4f3d | |
kolaente | a7461d1ddd | |
kolaente | a451189bb6 | |
kolaente | bf9af27fc3 | |
kolaente | 5619fda0f2 | |
kolaente | 167953b26b | |
kolaente | 664bf0a5f4 | |
kolaente | 5e991f3024 | |
kolaente | 28050d9cd5 | |
kolaente | e94b71d577 | |
renovate | 336ce217d3 | |
Frederick [Bot] | ce01085951 | |
kolaente | 96a6d43a3f | |
kolaente | 13d63e34aa | |
renovate | a8441c72b8 | |
renovate | 230fa6ce66 | |
renovate | 069c491fbd | |
renovate | a9eae95d67 | |
renovate | 50502d9d11 | |
renovate | 18af6edc82 | |
renovate | d048b61eb3 | |
renovate | 996607e670 | |
renovate | e33ebe1831 | |
renovate | 557b0ffec7 | |
renovate | dae6cdb9d7 | |
renovate | 158e4d690f | |
renovate | 691eb84a99 | |
renovate | 698ee7e163 | |
renovate | ce822573df | |
renovate | 198abee01d |
16
.drone.yml
16
.drone.yml
|
@ -42,7 +42,7 @@ steps:
|
|||
# - .cache
|
||||
|
||||
- name: dependencies
|
||||
image: node:20.10-alpine
|
||||
image: node:20.11.0-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
@ -55,7 +55,7 @@ steps:
|
|||
# - restore-cache
|
||||
|
||||
- name: lint
|
||||
image: node:20.10-alpine
|
||||
image: node:20.11.0-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
@ -66,7 +66,7 @@ steps:
|
|||
- dependencies
|
||||
|
||||
- name: build-prod
|
||||
image: node:20.10-alpine
|
||||
image: node:20.11.0-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
@ -77,7 +77,7 @@ steps:
|
|||
- dependencies
|
||||
|
||||
- name: test-unit
|
||||
image: node:20.10-alpine
|
||||
image: node:20.11.0-alpine
|
||||
pull: always
|
||||
commands:
|
||||
- corepack enable && pnpm config set store-dir .cache/pnpm
|
||||
|
@ -87,7 +87,7 @@ steps:
|
|||
|
||||
- name: typecheck
|
||||
failure: ignore
|
||||
image: node:20.10-alpine
|
||||
image: node:20.11.0-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
@ -202,7 +202,7 @@ steps:
|
|||
# - .cache
|
||||
|
||||
- name: build
|
||||
image: node:20.10-alpine
|
||||
image: node:20.11.0-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
@ -285,7 +285,7 @@ steps:
|
|||
# - .cache
|
||||
|
||||
- name: build
|
||||
image: node:20.10-alpine
|
||||
image: node:20.11.0-alpine
|
||||
pull: always
|
||||
environment:
|
||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
|
@ -532,6 +532,6 @@ steps:
|
|||
src/i18n/lang/en.json: en.json
|
||||
---
|
||||
kind: signature
|
||||
hmac: ecb706a867b39f2501cc6cf587a535fe4cd6cfd0c339833a733d61a3349c5a54
|
||||
hmac: a044c7c4db3c2a11299d4d118397e9d25be36db241723a1bbd0a2f9cc90ffdac
|
||||
|
||||
...
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# │─││ │││ │ │
|
||||
# ┘─┘┘─┘┘┘─┘┘─┘
|
||||
|
||||
FROM --platform=$BUILDPLATFORM node:20.10-alpine AS builder
|
||||
FROM --platform=$BUILDPLATFORM node:20.11.0-alpine AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
|
|
|
@ -541,6 +541,86 @@ describe('Task', () => {
|
|||
.should('contain', 'Success')
|
||||
})
|
||||
|
||||
it('Can set a due date to a specific date for a task', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
done: false,
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Set Due Date')
|
||||
.click()
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Due Date')
|
||||
.get('.date-input .datepicker .show')
|
||||
.click()
|
||||
cy.get('.datepicker-popup .flatpickr-innerContainer .flatpickr-days .flatpickr-day.today')
|
||||
.click()
|
||||
cy.get('[data-cy="closeDatepicker"]')
|
||||
.contains('Confirm')
|
||||
.click()
|
||||
|
||||
const today = new Date()
|
||||
const day = today.toLocaleString('default', {day: '2-digit'})
|
||||
const month = today.toLocaleString('default', {month: 'short'})
|
||||
const year = today.toLocaleString('default', {year: 'numeric'})
|
||||
const date = `${day} ${month} ${year}, 12:00:00`
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Due Date')
|
||||
.get('.date-input .datepicker-popup')
|
||||
.should('not.exist')
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Due Date')
|
||||
.get('.date-input')
|
||||
.should('contain.text', date)
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
||||
it('Can change a due date to a specific date for a task', () => {
|
||||
const dueDate = new Date()
|
||||
dueDate.setHours(12)
|
||||
dueDate.setMinutes(0)
|
||||
dueDate.setSeconds(0)
|
||||
dueDate.setDate(1)
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
done: false,
|
||||
due_date: dueDate.toISOString(),
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Set Due Date')
|
||||
.click()
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Due Date')
|
||||
.get('.date-input .datepicker .show')
|
||||
.click()
|
||||
cy.get('.datepicker-popup .flatpickr-innerContainer .flatpickr-days .flatpickr-day.today')
|
||||
.click()
|
||||
cy.get('[data-cy="closeDatepicker"]')
|
||||
.contains('Confirm')
|
||||
.click()
|
||||
|
||||
const today = new Date()
|
||||
const day = today.toLocaleString('default', {day: '2-digit'})
|
||||
const month = today.toLocaleString('default', {month: 'short'})
|
||||
const year = today.toLocaleString('default', {year: 'numeric'})
|
||||
const date = `${day} ${month} ${year}, 12:00:00`
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Due Date')
|
||||
.get('.date-input .datepicker-popup')
|
||||
.should('not.exist')
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Due Date')
|
||||
.get('.date-input')
|
||||
.should('contain.text', date)
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
||||
it('Can set a reminder', () => {
|
||||
TaskReminderFactory.truncate()
|
||||
const tasks = TaskFactory.create(1, {
|
||||
|
@ -645,7 +725,7 @@ describe('Task', () => {
|
|||
.click()
|
||||
cy.get('.reminder-options-popup .card-content .reminder-period input')
|
||||
.first()
|
||||
.type('10')
|
||||
.type('{selectall}10')
|
||||
cy.get('.reminder-options-popup .card-content .reminder-period select')
|
||||
.first()
|
||||
.select('days')
|
||||
|
@ -771,7 +851,7 @@ describe('Task', () => {
|
|||
.should('exist')
|
||||
})
|
||||
|
||||
it.only('Can check items off a checklist', () => {
|
||||
it('Can check items off a checklist', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
description: `
|
||||
|
@ -858,7 +938,7 @@ describe('Task', () => {
|
|||
method: 'PUT',
|
||||
url: `${Cypress.env('API_URL')}/tasks/${tasks[0].id}/attachments`,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${window.localStorage.getItem('token')}`,
|
||||
'Authorization': `Bearer ${window.localStorage.getItem('token')}`,
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
body: formData,
|
||||
|
|
136
package.json
136
package.json
|
@ -13,7 +13,7 @@
|
|||
},
|
||||
"homepage": "https://vikunja.io/",
|
||||
"funding": "https://opencollective.com/vikunja",
|
||||
"packageManager": "pnpm@8.12.1",
|
||||
"packageManager": "pnpm@8.14.1",
|
||||
"keywords": [
|
||||
"todo",
|
||||
"productivity",
|
||||
|
@ -52,57 +52,57 @@
|
|||
"@github/hotkey": "3.1.0",
|
||||
"@infectoone/vue-ganttastic": "2.2.0",
|
||||
"@intlify/unplugin-vue-i18n": "2.0.0",
|
||||
"@kyvg/vue3-notification": "3.1.2",
|
||||
"@sentry/tracing": "7.88.0",
|
||||
"@sentry/vue": "7.88.0",
|
||||
"@tiptap/core": "2.1.13",
|
||||
"@tiptap/extension-blockquote": "2.1.13",
|
||||
"@tiptap/extension-bold": "2.1.13",
|
||||
"@tiptap/extension-bullet-list": "2.1.13",
|
||||
"@tiptap/extension-code": "2.1.13",
|
||||
"@tiptap/extension-code-block-lowlight": "2.1.13",
|
||||
"@tiptap/extension-document": "2.1.13",
|
||||
"@tiptap/extension-dropcursor": "2.1.13",
|
||||
"@tiptap/extension-gapcursor": "2.1.13",
|
||||
"@tiptap/extension-hard-break": "2.1.13",
|
||||
"@tiptap/extension-heading": "2.1.13",
|
||||
"@tiptap/extension-history": "2.1.13",
|
||||
"@tiptap/extension-horizontal-rule": "2.1.13",
|
||||
"@tiptap/extension-image": "2.1.13",
|
||||
"@tiptap/extension-italic": "2.1.13",
|
||||
"@tiptap/extension-link": "2.1.13",
|
||||
"@tiptap/extension-list-item": "2.1.13",
|
||||
"@tiptap/extension-ordered-list": "2.1.13",
|
||||
"@tiptap/extension-paragraph": "2.1.13",
|
||||
"@tiptap/extension-placeholder": "2.1.13",
|
||||
"@tiptap/extension-strike": "2.1.13",
|
||||
"@tiptap/extension-table": "2.1.13",
|
||||
"@tiptap/extension-table-cell": "2.1.13",
|
||||
"@tiptap/extension-table-header": "2.1.13",
|
||||
"@tiptap/extension-table-row": "2.1.13",
|
||||
"@tiptap/extension-task-item": "2.1.13",
|
||||
"@tiptap/extension-task-list": "2.1.13",
|
||||
"@tiptap/extension-text": "2.1.13",
|
||||
"@tiptap/extension-typography": "2.1.13",
|
||||
"@tiptap/extension-underline": "2.1.13",
|
||||
"@tiptap/pm": "2.1.13",
|
||||
"@tiptap/suggestion": "2.1.13",
|
||||
"@tiptap/vue-3": "2.1.13",
|
||||
"@kyvg/vue3-notification": "3.1.3",
|
||||
"@sentry/tracing": "7.94.1",
|
||||
"@sentry/vue": "7.94.1",
|
||||
"@tiptap/core": "2.1.16",
|
||||
"@tiptap/extension-blockquote": "2.1.16",
|
||||
"@tiptap/extension-bold": "2.1.16",
|
||||
"@tiptap/extension-bullet-list": "2.1.16",
|
||||
"@tiptap/extension-code": "2.1.16",
|
||||
"@tiptap/extension-code-block-lowlight": "2.1.16",
|
||||
"@tiptap/extension-document": "2.1.16",
|
||||
"@tiptap/extension-dropcursor": "2.1.16",
|
||||
"@tiptap/extension-gapcursor": "2.1.16",
|
||||
"@tiptap/extension-hard-break": "2.1.16",
|
||||
"@tiptap/extension-heading": "2.1.16",
|
||||
"@tiptap/extension-history": "2.1.16",
|
||||
"@tiptap/extension-horizontal-rule": "2.1.16",
|
||||
"@tiptap/extension-image": "2.1.16",
|
||||
"@tiptap/extension-italic": "2.1.16",
|
||||
"@tiptap/extension-link": "2.1.16",
|
||||
"@tiptap/extension-list-item": "2.1.16",
|
||||
"@tiptap/extension-ordered-list": "2.1.16",
|
||||
"@tiptap/extension-paragraph": "2.1.16",
|
||||
"@tiptap/extension-placeholder": "2.1.16",
|
||||
"@tiptap/extension-strike": "2.1.16",
|
||||
"@tiptap/extension-table": "2.1.16",
|
||||
"@tiptap/extension-table-cell": "2.1.16",
|
||||
"@tiptap/extension-table-header": "2.1.16",
|
||||
"@tiptap/extension-table-row": "2.1.16",
|
||||
"@tiptap/extension-task-item": "2.1.16",
|
||||
"@tiptap/extension-task-list": "2.1.16",
|
||||
"@tiptap/extension-text": "2.1.16",
|
||||
"@tiptap/extension-typography": "2.1.16",
|
||||
"@tiptap/extension-underline": "2.1.16",
|
||||
"@tiptap/pm": "2.1.16",
|
||||
"@tiptap/suggestion": "2.1.16",
|
||||
"@tiptap/vue-3": "2.1.16",
|
||||
"@types/is-touch-device": "1.0.2",
|
||||
"@types/lodash.clonedeep": "4.5.9",
|
||||
"@vueuse/core": "10.7.0",
|
||||
"@vueuse/router": "10.7.0",
|
||||
"axios": "1.6.2",
|
||||
"@vueuse/core": "10.7.2",
|
||||
"@vueuse/router": "10.7.2",
|
||||
"axios": "1.6.5",
|
||||
"blurhash": "2.0.5",
|
||||
"bulma-css-variables": "0.9.33",
|
||||
"camel-case": "4.1.2",
|
||||
"date-fns": "3.2.0",
|
||||
"date-fns": "3.3.0",
|
||||
"dayjs": "1.11.10",
|
||||
"dompurify": "3.0.6",
|
||||
"dompurify": "3.0.8",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"flatpickr": "4.6.13",
|
||||
"flexsearch": "0.7.31",
|
||||
"floating-vue": "2.0.0-beta.24",
|
||||
"floating-vue": "5.2.0",
|
||||
"is-touch-device": "1.0.1",
|
||||
"klona": "2.0.6",
|
||||
"lodash.debounce": "4.0.8",
|
||||
|
@ -110,25 +110,25 @@
|
|||
"pinia": "2.1.7",
|
||||
"register-service-worker": "1.7.2",
|
||||
"snake-case": "3.0.4",
|
||||
"sortablejs": "1.15.1",
|
||||
"sortablejs": "1.15.2",
|
||||
"tippy.js": "6.3.7",
|
||||
"ufo": "1.3.2",
|
||||
"vue": "3.3.13",
|
||||
"vue": "3.4.15",
|
||||
"vue-advanced-cropper": "2.8.8",
|
||||
"vue-flatpickr-component": "11.0.3",
|
||||
"vue-i18n": "9.8.0",
|
||||
"vue-i18n": "9.9.0",
|
||||
"vue-router": "4.2.5",
|
||||
"workbox-precaching": "7.0.0",
|
||||
"zhyswan-vuedraggable": "4.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@4tw/cypress-drag-drop": "2.2.5",
|
||||
"@cypress/vite-dev-server": "5.0.6",
|
||||
"@cypress/vite-dev-server": "5.0.7",
|
||||
"@cypress/vue": "6.0.0",
|
||||
"@faker-js/faker": "8.3.1",
|
||||
"@histoire/plugin-screenshot": "0.17.6",
|
||||
"@histoire/plugin-vue": "0.17.6",
|
||||
"@rushstack/eslint-patch": "1.6.1",
|
||||
"@histoire/plugin-screenshot": "0.17.8",
|
||||
"@histoire/plugin-vue": "0.17.8",
|
||||
"@rushstack/eslint-patch": "1.7.0",
|
||||
"@tsconfig/node18": "18.2.2",
|
||||
"@types/codemirror": "5.60.15",
|
||||
"@types/dompurify": "3.0.5",
|
||||
|
@ -136,44 +136,44 @@
|
|||
"@types/is-touch-device": "1.0.2",
|
||||
"@types/lodash.debounce": "4.0.9",
|
||||
"@types/marked": "5.0.2",
|
||||
"@types/node": "20.10.5",
|
||||
"@types/node": "20.11.5",
|
||||
"@types/postcss-preset-env": "7.7.0",
|
||||
"@types/sortablejs": "1.15.7",
|
||||
"@typescript-eslint/eslint-plugin": "6.15.0",
|
||||
"@typescript-eslint/parser": "6.15.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.19.0",
|
||||
"@typescript-eslint/parser": "6.19.0",
|
||||
"@vitejs/plugin-legacy": "5.2.0",
|
||||
"@vitejs/plugin-vue": "4.5.2",
|
||||
"@vitejs/plugin-vue": "5.0.3",
|
||||
"@vue/eslint-config-typescript": "12.0.0",
|
||||
"@vue/test-utils": "2.4.3",
|
||||
"@vue/tsconfig": "0.5.1",
|
||||
"autoprefixer": "10.4.16",
|
||||
"autoprefixer": "10.4.17",
|
||||
"browserslist": "4.22.2",
|
||||
"caniuse-lite": "1.0.30001570",
|
||||
"caniuse-lite": "1.0.30001579",
|
||||
"css-has-pseudo": "6.0.1",
|
||||
"csstype": "3.1.3",
|
||||
"cypress": "13.6.1",
|
||||
"esbuild": "0.19.10",
|
||||
"cypress": "13.6.3",
|
||||
"esbuild": "0.19.11",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-plugin-vue": "9.19.2",
|
||||
"happy-dom": "12.10.3",
|
||||
"histoire": "0.17.6",
|
||||
"postcss": "8.4.32",
|
||||
"eslint-plugin-vue": "9.20.1",
|
||||
"happy-dom": "13.2.0",
|
||||
"histoire": "0.17.8",
|
||||
"postcss": "8.4.33",
|
||||
"postcss-easing-gradients": "3.0.1",
|
||||
"postcss-easings": "4.0.0",
|
||||
"postcss-focus-within": "8.0.1",
|
||||
"postcss-preset-env": "9.3.0",
|
||||
"rollup": "4.9.1",
|
||||
"rollup-plugin-visualizer": "5.11.0",
|
||||
"sass": "1.69.5",
|
||||
"rollup": "4.9.5",
|
||||
"rollup-plugin-visualizer": "5.12.0",
|
||||
"sass": "1.70.0",
|
||||
"start-server-and-test": "2.0.3",
|
||||
"typescript": "5.3.3",
|
||||
"vite": "5.0.10",
|
||||
"vite": "5.0.12",
|
||||
"vite-plugin-inject-preload": "1.3.3",
|
||||
"vite-plugin-pwa": "0.17.4",
|
||||
"vite-plugin-sentry": "1.3.0",
|
||||
"vite-svg-loader": "5.1.0",
|
||||
"vitest": "1.0.4",
|
||||
"vue-tsc": "1.8.25",
|
||||
"vitest": "1.2.1",
|
||||
"vue-tsc": "1.8.27",
|
||||
"wait-on": "7.2.0",
|
||||
"workbox-cli": "7.0.0"
|
||||
},
|
||||
|
|
2124
pnpm-lock.yaml
2124
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -4,6 +4,11 @@
|
|||
"extends": [
|
||||
"config:js-app"
|
||||
],
|
||||
"hostRules": [
|
||||
{
|
||||
"timeout": 600000
|
||||
}
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackageNames": ["happy-dom"],
|
||||
|
|
|
@ -37,8 +37,6 @@ import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
|
|||
import Ready from '@/components/misc/ready.vue'
|
||||
|
||||
import {setLanguage} from '@/i18n'
|
||||
import AccountDeleteService from '@/services/accountDelete'
|
||||
import {success} from '@/message'
|
||||
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
|
@ -48,6 +46,9 @@ import {useBodyClass} from '@/composables/useBodyClass'
|
|||
import AddToHomeScreen from '@/components/home/AddToHomeScreen.vue'
|
||||
import DemoMode from '@/components/home/DemoMode.vue'
|
||||
|
||||
const importAccountDeleteService = () => import('@/services/accountDelete')
|
||||
const importMessage = () => import('@/message')
|
||||
|
||||
const baseStore = useBaseStore()
|
||||
const authStore = useAuthStore()
|
||||
const router = useRouter()
|
||||
|
@ -68,8 +69,11 @@ watch(accountDeletionConfirm, async (accountDeletionConfirm) => {
|
|||
return
|
||||
}
|
||||
|
||||
const messageP = importMessage()
|
||||
const AccountDeleteService = (await importAccountDeleteService()).default
|
||||
const accountDeletionService = new AccountDeleteService()
|
||||
await accountDeletionService.confirm(accountDeletionConfirm)
|
||||
const {success} = await messageP
|
||||
success({message: t('user.deletion.confirmSuccess')})
|
||||
authStore.refreshUserInfo()
|
||||
}, { immediate: true })
|
||||
|
|
|
@ -128,6 +128,12 @@ const flatPickrDate = computed({
|
|||
return
|
||||
}
|
||||
|
||||
if (date.value !== null) {
|
||||
const oldDate = formatDate(date.value, 'yyy-LL-dd H:mm')
|
||||
if (oldDate === newValue) {
|
||||
return
|
||||
}
|
||||
}
|
||||
date.value = createDateFromString(newValue)
|
||||
updateData()
|
||||
},
|
||||
|
@ -155,10 +161,6 @@ function updateData() {
|
|||
}
|
||||
|
||||
function setDate(dateString: string) {
|
||||
if (date.value === null) {
|
||||
date.value = new Date()
|
||||
}
|
||||
|
||||
const interval = calculateDayInterval(dateString)
|
||||
const newDate = new Date()
|
||||
newDate.setDate(newDate.getDate() + interval)
|
||||
|
@ -166,7 +168,6 @@ function setDate(dateString: string) {
|
|||
newDate.setMinutes(0)
|
||||
newDate.setSeconds(0)
|
||||
date.value = newDate
|
||||
flatPickrDate.value = newDate
|
||||
updateData()
|
||||
}
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@
|
|||
v-tooltip="$t('input.editor.bulletList')"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon :icon="['fa', 'fa-list-ol']"/>
|
||||
<icon :icon="['fa', 'fa-list-ul']"/>
|
||||
</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
|
@ -120,7 +120,7 @@
|
|||
v-tooltip="$t('input.editor.orderedList')"
|
||||
>
|
||||
<span class="icon">
|
||||
<icon :icon="['fa', 'fa-list-ul']"/>
|
||||
<icon :icon="['fa', 'fa-list-ol']"/>
|
||||
</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
|
|
|
@ -671,36 +671,17 @@ watch(
|
|||
line-height: 1.1;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #68cef8;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: rgba(#616161, 0.1);
|
||||
color: #616161;
|
||||
background-color: var(--grey-200);
|
||||
color: var(--grey-700);
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #0d0d0d;
|
||||
color: #fff;
|
||||
background: var(--grey-200);
|
||||
color: var(--grey-700);
|
||||
font-family: 'JetBrainsMono', monospace;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
code {
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #0d0d0d;
|
||||
color: #fff;
|
||||
font-family: 'JetBrainsMono', monospace;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
border-radius: $radius;
|
||||
|
||||
code {
|
||||
color: inherit;
|
||||
|
@ -711,7 +692,7 @@ watch(
|
|||
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #616161;
|
||||
color: var(--grey-500);
|
||||
}
|
||||
|
||||
.hljs-variable,
|
||||
|
@ -724,7 +705,7 @@ watch(
|
|||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class {
|
||||
color: #f98181;
|
||||
color: var(--code-variable);
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
|
@ -734,23 +715,23 @@ watch(
|
|||
.hljs-literal,
|
||||
.hljs-type,
|
||||
.hljs-params {
|
||||
color: #fbbc88;
|
||||
color: var(--code-literal);
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet {
|
||||
color: #b9f18d;
|
||||
color: var(--code-symbol);
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: #faf594;
|
||||
color: var(--code-section);
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
color: #70cff8;
|
||||
color: var(--code-keyword);
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<slot name="trigger" :isOpen="open" :toggle="toggle"></slot>
|
||||
<slot name="trigger" :isOpen="open" :toggle="toggle" :close="close"></slot>
|
||||
<div
|
||||
class="popup"
|
||||
:class="{
|
||||
|
@ -8,7 +8,7 @@
|
|||
}"
|
||||
ref="popup"
|
||||
>
|
||||
<slot name="content" :isOpen="open" :toggle="toggle"/>
|
||||
<slot name="content" :isOpen="open" :toggle="toggle" :close="close"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ import Multiselect from '@/components/input/multiselect.vue'
|
|||
import type {ILabel} from '@/modelTypes/ILabel'
|
||||
import {useLabelStore} from '@/stores/labels'
|
||||
import {useTaskStore} from '@/stores/tasks'
|
||||
import {getRandomColorHex} from '@/helpers/color/randomColor'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
|
@ -132,7 +133,10 @@ async function createAndAddLabel(title: string) {
|
|||
return
|
||||
}
|
||||
|
||||
const newLabel = await labelStore.createLabel(new LabelModel({title}))
|
||||
const newLabel = await labelStore.createLabel(new LabelModel({
|
||||
title,
|
||||
hexColor: getRandomColorHex(),
|
||||
}))
|
||||
addLabel(newLabel, false)
|
||||
labels.value.push(newLabel)
|
||||
success({message: t('task.label.addCreateSuccess')})
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{{ reminderText }}
|
||||
</SimpleButton>
|
||||
</template>
|
||||
<template #content="{isOpen, toggle}">
|
||||
<template #content="{isOpen, close}">
|
||||
<Card class="reminder-options-popup" :class="{'is-open': isOpen}" :padding="false">
|
||||
<div class="options" v-if="activeForm === null">
|
||||
<SimpleButton
|
||||
|
@ -17,7 +17,7 @@
|
|||
:key="k"
|
||||
class="option-button"
|
||||
:class="{'currently-active': p.relativePeriod === modelValue?.relativePeriod && modelValue?.relativeTo === p.relativeTo}"
|
||||
@click="setReminderFromPreset(p, toggle)"
|
||||
@click="setReminderFromPreset(p, close)"
|
||||
>
|
||||
{{ formatReminder(p) }}
|
||||
</SimpleButton>
|
||||
|
@ -40,20 +40,20 @@
|
|||
<ReminderPeriod
|
||||
v-if="activeForm === 'relative'"
|
||||
v-model="reminder"
|
||||
@update:modelValue="updateDataAndMaybeClose(toggle)"
|
||||
@update:modelValue="updateDataAndMaybeClose(close)"
|
||||
/>
|
||||
|
||||
<DatepickerInline
|
||||
v-if="activeForm === 'absolute'"
|
||||
v-model="reminderDate"
|
||||
@update:modelValue="setReminderDate(toggle)"
|
||||
@update:modelValue="setReminderDate(close)"
|
||||
/>
|
||||
|
||||
<x-button
|
||||
v-if="showFormSwitch !== null"
|
||||
class="reminder__close-button"
|
||||
:shadow="false"
|
||||
@click="toggle"
|
||||
@click="updateDataAndMaybeClose(close)"
|
||||
>
|
||||
{{ $t('misc.confirm') }}
|
||||
</x-button>
|
||||
|
@ -148,25 +148,26 @@ function updateData() {
|
|||
}
|
||||
}
|
||||
|
||||
function setReminderDate(toggle) {
|
||||
function setReminderDate(close) {
|
||||
reminder.value.reminder = reminderDate.value === null
|
||||
? null
|
||||
: new Date(reminderDate.value)
|
||||
reminder.value.relativeTo = null
|
||||
reminder.value.relativePeriod = 0
|
||||
updateDataAndMaybeClose(toggle)
|
||||
updateDataAndMaybeClose(close)
|
||||
}
|
||||
|
||||
function setReminderFromPreset(preset, toggle) {
|
||||
|
||||
function setReminderFromPreset(preset, close) {
|
||||
reminder.value = preset
|
||||
updateData()
|
||||
toggle()
|
||||
close()
|
||||
}
|
||||
|
||||
function updateDataAndMaybeClose(toggle) {
|
||||
function updateDataAndMaybeClose(close) {
|
||||
updateData()
|
||||
if (clearAfterUpdate) {
|
||||
toggle()
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ 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'
|
||||
|
||||
const {
|
||||
modelValue,
|
||||
|
@ -105,7 +106,7 @@ function updateData() {
|
|||
reminder.value.relativeTo = period.value.relativeTo
|
||||
reminder.value.reminder = null
|
||||
|
||||
emit('update:modelValue', reminder.value)
|
||||
useDebounceFn(() => emit('update:modelValue', reminder.value), 1000)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -288,6 +288,7 @@ async function markAsDone(checked: boolean) {
|
|||
title: t('task.undo'),
|
||||
callback: () => undoDone(checked),
|
||||
}])
|
||||
updateDueDate()
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* @param color
|
||||
* @returns {string}
|
||||
*/
|
||||
export function colorFromHex(color: string) {
|
||||
if (color.substring(0, 1) === '#') {
|
||||
export function colorFromHex(color: string): string {
|
||||
if (color !== '' && color.substring(0, 1) === '#') {
|
||||
color = color.substring(1, 7)
|
||||
}
|
||||
|
||||
|
|
|
@ -15,34 +15,32 @@ interface dateFoundResult {
|
|||
const monthsRegexGroup = '(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)'
|
||||
|
||||
function matchesDateExpr(text: string, dateExpr: string): boolean {
|
||||
return text.match(new RegExp('(^| )' + dateExpr, 'g')) !== null
|
||||
return text.match(new RegExp('(^| )' + dateExpr, 'gi')) !== null
|
||||
}
|
||||
|
||||
export const parseDate = (text: string, now: Date = new Date()): dateParseResult => {
|
||||
const lowerText: string = text.toLowerCase()
|
||||
|
||||
if (matchesDateExpr(lowerText, 'today')) {
|
||||
if (matchesDateExpr(text, 'today')) {
|
||||
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('today')), 'today')
|
||||
}
|
||||
if (matchesDateExpr(lowerText, 'tomorrow')) {
|
||||
if (matchesDateExpr(text, 'tomorrow')) {
|
||||
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('tomorrow')), 'tomorrow')
|
||||
}
|
||||
if (matchesDateExpr(lowerText, 'next monday')) {
|
||||
if (matchesDateExpr(text, 'next monday')) {
|
||||
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('nextMonday')), 'next monday')
|
||||
}
|
||||
if (matchesDateExpr(lowerText, 'this weekend')) {
|
||||
if (matchesDateExpr(text, 'this weekend')) {
|
||||
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('thisWeekend')), 'this weekend')
|
||||
}
|
||||
if (matchesDateExpr(lowerText, 'later this week')) {
|
||||
if (matchesDateExpr(text, 'later this week')) {
|
||||
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('laterThisWeek')), 'later this week')
|
||||
}
|
||||
if (matchesDateExpr(lowerText, 'later next week')) {
|
||||
if (matchesDateExpr(text, 'later next week')) {
|
||||
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('laterNextWeek')), 'later next week')
|
||||
}
|
||||
if (matchesDateExpr(lowerText, 'next week')) {
|
||||
if (matchesDateExpr(text, 'next week')) {
|
||||
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('nextWeek')), 'next week')
|
||||
}
|
||||
if (matchesDateExpr(lowerText, 'next month')) {
|
||||
if (matchesDateExpr(text, 'next month')) {
|
||||
const date: Date = new Date()
|
||||
date.setDate(1)
|
||||
date.setMonth(date.getMonth() + 1)
|
||||
|
@ -52,7 +50,7 @@ export const parseDate = (text: string, now: Date = new Date()): dateParseResult
|
|||
|
||||
return addTimeToDate(text, date, 'next month')
|
||||
}
|
||||
if (matchesDateExpr(lowerText, 'end of month')) {
|
||||
if (matchesDateExpr(text, 'end of month')) {
|
||||
const curDate: Date = new Date()
|
||||
const date: Date = new Date(curDate.getFullYear(), curDate.getMonth() + 1, 0)
|
||||
date.setHours(calculateNearestHours(date))
|
||||
|
@ -70,7 +68,7 @@ export const parseDate = (text: string, now: Date = new Date()): dateParseResult
|
|||
parsed = getDayFromText(text)
|
||||
if (parsed.date !== null) {
|
||||
const month = getMonthFromText(text, parsed.date)
|
||||
return addTimeToDate(text, month.date, parsed.foundText)
|
||||
return addTimeToDate(month.newText, month.date, parsed.foundText)
|
||||
}
|
||||
|
||||
parsed = getDateFromTextIn(text, now)
|
||||
|
@ -123,7 +121,7 @@ const addTimeToDate = (text: string, date: Date, previousMatch: string | null):
|
|||
|
||||
const replace = results !== null ? results[0] : previousMatch
|
||||
return {
|
||||
newText: replaceAll(text, replace, ''),
|
||||
newText: replaceAll(text, replace, '').trim(),
|
||||
date: date,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@
|
|||
"expired": "Žeton je potekel pred {ago}.",
|
||||
"tokenCreatedSuccess": "Tu je vaš novi API žeton: {token}",
|
||||
"tokenCreatedNotSeeAgain": "Shranite ga na varno mesto, ker ga ne boste več videli!",
|
||||
"selectAll": "Select all",
|
||||
"selectAll": "Izberi vse",
|
||||
"delete": {
|
||||
"header": "Izbriši ta žeton",
|
||||
"text1": "Ali ste prepričani, da želite izbrisati žeton \"{token}\"?",
|
||||
|
|
|
@ -5,7 +5,6 @@ import type {ILabel} from '@/modelTypes/ILabel'
|
|||
import type {IUser} from '@/modelTypes/IUser'
|
||||
|
||||
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
||||
import {getRandomColorHex} from '@/helpers/color/randomColor'
|
||||
|
||||
export default class LabelModel extends AbstractModel<ILabel> implements ILabel {
|
||||
id = 0
|
||||
|
@ -24,12 +23,8 @@ export default class LabelModel extends AbstractModel<ILabel> implements ILabel
|
|||
constructor(data: Partial<ILabel> = {}) {
|
||||
super()
|
||||
this.assignData(data)
|
||||
|
||||
if (this.hexColor === '') {
|
||||
this.hexColor = getRandomColorHex()
|
||||
}
|
||||
|
||||
if (this.hexColor.substring(0, 1) !== '#') {
|
||||
if (this.hexColor !== '' && this.hexColor.substring(0, 1) !== '#') {
|
||||
this.hexColor = '#' + this.hexColor
|
||||
}
|
||||
this.textColor = colorIsDark(this.hexColor) ? '#4a4a4a' : '#fff'
|
||||
|
|
|
@ -112,6 +112,16 @@ describe('Parse Task Text', () => {
|
|||
expect(result?.date?.getMonth()).toBe(tomorrow.getMonth())
|
||||
expect(result?.date?.getDate()).toBe(tomorrow.getDate())
|
||||
})
|
||||
it('should recognize Tomorrow', () => {
|
||||
const result = parseTaskText('Lorem Ipsum Tomorrow')
|
||||
|
||||
expect(result.text).toBe('Lorem Ipsum')
|
||||
const tomorrow = new Date()
|
||||
tomorrow.setDate(tomorrow.getDate() + 1)
|
||||
expect(result?.date?.getFullYear()).toBe(tomorrow.getFullYear())
|
||||
expect(result?.date?.getMonth()).toBe(tomorrow.getMonth())
|
||||
expect(result?.date?.getDate()).toBe(tomorrow.getDate())
|
||||
})
|
||||
it('should recognize next monday', () => {
|
||||
const result = parseTaskText('Lorem Ipsum next monday')
|
||||
|
||||
|
@ -441,7 +451,7 @@ describe('Parse Task Text', () => {
|
|||
'06/08/2021': '2021-6-8',
|
||||
'6/7/21': '2021-6-7',
|
||||
'27/07/2021,': null,
|
||||
'2021/07/06,': '2021-7-6',
|
||||
'2021/07/06': '2021-7-6',
|
||||
'2021-07-06': '2021-7-6',
|
||||
'27 jan': '2022-1-27',
|
||||
'27/1': '2022-1-27',
|
||||
|
@ -449,39 +459,52 @@ describe('Parse Task Text', () => {
|
|||
'16/12': '2021-12-16',
|
||||
'01/27': '2022-1-27',
|
||||
'1/27': '2022-1-27',
|
||||
'Jan 27': '2022-1-27',
|
||||
'jan 27': '2022-1-27',
|
||||
'Jan 27': '2022-1-27',
|
||||
'feb 21': '2022-2-21',
|
||||
'Feb 21': '2022-2-21',
|
||||
'mar 21': '2022-3-21',
|
||||
'Mar 21': '2022-3-21',
|
||||
'apr 21': '2022-4-21',
|
||||
'Apr 21': '2022-4-21',
|
||||
'may 21': '2022-5-21',
|
||||
'May 21': '2022-5-21',
|
||||
'jun 21': '2022-6-21',
|
||||
'Jun 21': '2022-6-21',
|
||||
'jul 21': '2021-7-21',
|
||||
'Jul 21': '2021-7-21',
|
||||
'aug 21': '2021-8-21',
|
||||
'Aug 21': '2021-8-21',
|
||||
'sep 21': '2021-9-21',
|
||||
'Sep 21': '2021-9-21',
|
||||
'oct 21': '2021-10-21',
|
||||
'Oct 21': '2021-10-21',
|
||||
'nov 21': '2021-11-21',
|
||||
'Nov 21': '2021-11-21',
|
||||
'dec 21': '2021-12-21',
|
||||
'Dec 21': '2021-12-21',
|
||||
} as Record<string, string | null>
|
||||
|
||||
for (const c in cases) {
|
||||
it(`should parse '${c}' as '${cases[c]}' with the date at the end`, () => {
|
||||
const {date} = getDateFromText(`Lorem Ipsum ${c}`, now)
|
||||
const {date, foundText} = getDateFromText(`Lorem Ipsum ${c}`, now)
|
||||
if (date === null && cases[c] === null) {
|
||||
expect(date).toBeNull()
|
||||
return
|
||||
}
|
||||
|
||||
expect(`${date?.getFullYear()}-${date?.getMonth() + 1}-${date?.getDate()}`).toBe(cases[c])
|
||||
expect(foundText.trim()).toBe(c)
|
||||
})
|
||||
it(`should parse '${c}' as '${cases[c]}' with the date at the beginning`, () => {
|
||||
const {date} = getDateFromText(`${c} Lorem Ipsum`, now)
|
||||
const {date, foundText} = getDateFromText(`${c} Lorem Ipsum`, now)
|
||||
if (date === null && cases[c] === null) {
|
||||
expect(date).toBeNull()
|
||||
return
|
||||
}
|
||||
|
||||
expect(`${date?.getFullYear()}-${date?.getMonth() + 1}-${date?.getDate()}`).toBe(cases[c])
|
||||
expect(foundText.trim()).toBe(c)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -532,6 +555,20 @@ describe('Parse Task Text', () => {
|
|||
expect(`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`).toBe(cases[c])
|
||||
})
|
||||
}
|
||||
|
||||
it('should replace the text in title case', () => {
|
||||
const {date, newText} = parseDate('Some task Mar 8th', now)
|
||||
|
||||
expect(`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`).toBe('2021-3-8 12:0')
|
||||
expect(newText).toBe('Some task')
|
||||
})
|
||||
|
||||
it('should replace the text in lowercase', () => {
|
||||
const {date, newText} = parseDate('Some task mar 8th', now)
|
||||
|
||||
expect(`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`).toBe('2021-3-8 12:0')
|
||||
expect(newText).toBe('Some task')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ export const useLabelStore = defineStore('label', () => {
|
|||
}
|
||||
|
||||
function setLabel(label: ILabel) {
|
||||
labels.value[label.id] = label
|
||||
labels.value[label.id] = {...label}
|
||||
update(label)
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import {useBaseStore} from '@/stores/base'
|
|||
import ProjectUserService from '@/services/projectUsers'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import TaskCollectionService from '@/services/taskCollection'
|
||||
import {getRandomColorHex} from '@/helpers/color/randomColor'
|
||||
|
||||
interface MatchedAssignee extends IUser {
|
||||
match: string,
|
||||
|
@ -337,7 +338,10 @@ export const useTaskStore = defineStore('task', () => {
|
|||
let label = validateLabel(Object.values(labelStore.labels), labelTitle)
|
||||
if (typeof label === 'undefined') {
|
||||
// label not found, create it
|
||||
const labelModel = new LabelModel({title: labelTitle})
|
||||
const labelModel = new LabelModel({
|
||||
title: labelTitle,
|
||||
hexColor: getRandomColorHex(),
|
||||
})
|
||||
label = await labelStore.createLabel(labelModel)
|
||||
}
|
||||
return label
|
||||
|
|
|
@ -256,6 +256,13 @@
|
|||
--card-border-color: var(--grey-200);
|
||||
--logo-text-color: hsl(180, 1%, 15%);
|
||||
|
||||
// Code colors
|
||||
--code-variable: #da2222;
|
||||
--code-literal: #fd8a09;
|
||||
--code-symbol: #0ead69;
|
||||
--code-section: #3a86ff;
|
||||
--code-keyword: #8338ec;
|
||||
|
||||
&.dark {
|
||||
@media screen {
|
||||
// Light mode colours reversed for dark mode
|
||||
|
@ -311,6 +318,13 @@
|
|||
--scheme-invert: var(--grey-900);
|
||||
--scheme-invert-bis: var(--grey-900);
|
||||
--scheme-invert-ter: var(--grey-800);
|
||||
|
||||
// Code colors
|
||||
--code-variable: #f98181;
|
||||
--code-literal: #fbbc88;
|
||||
--code-symbol: #b9f18d;
|
||||
--code-section: #faf594;
|
||||
--code-keyword: #70cff8;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -150,8 +150,8 @@ function deleteLabel(label: ILabel) {
|
|||
}
|
||||
|
||||
function editLabelSubmit() {
|
||||
return labelStore.updateLabel(labelEditLabel.value)
|
||||
}
|
||||
return labelStore.updateLabel(labelEditLabel.value)
|
||||
}
|
||||
|
||||
function editLabel(label: ILabel) {
|
||||
if (label.createdBy.id !== userInfo.value.id) {
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, ref} from 'vue'
|
||||
import {computed, onBeforeMount, ref} from 'vue'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {useRouter} from 'vue-router'
|
||||
|
||||
|
@ -46,6 +46,7 @@ import LabelModel from '@/models/label'
|
|||
import {useLabelStore} from '@/stores/labels'
|
||||
import {useTitle} from '@/composables/useTitle'
|
||||
import {success} from '@/message'
|
||||
import {getRandomColorHex} from '@/helpers/color/randomColor'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
|
@ -55,6 +56,8 @@ useTitle(() => t('label.create.title'))
|
|||
const labelStore = useLabelStore()
|
||||
const label = ref(new LabelModel())
|
||||
|
||||
onBeforeMount(() => label.value.hexColor = getRandomColorHex())
|
||||
|
||||
const showError = ref(false)
|
||||
const loading = computed(() => labelStore.isLoading)
|
||||
|
||||
|
|
|
@ -179,7 +179,23 @@ watch(
|
|||
if (projectId < 0) {
|
||||
return
|
||||
}
|
||||
tasks.value = tasks.value.filter(t => typeof t.relatedTasks?.parenttask === 'undefined')
|
||||
const tasksById = {}
|
||||
tasks.value.forEach(t => tasksById[t.id] = true)
|
||||
|
||||
tasks.value = tasks.value.filter(t => {
|
||||
if (typeof t.relatedTasks?.parenttask === 'undefined') {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the task is a subtask, make sure the parent task is available in the current view as well
|
||||
for (const pt of t.relatedTasks.parenttask) {
|
||||
if(typeof tasksById[pt.id] === 'undefined') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
|
|
Reference in New Issue