Compare commits

..

33 Commits

Author SHA1 Message Date
renovate 898707664c fix(deps): update sentry-javascript monorepo to v7.94.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-19 13:19:38 +00:00
renovate d0b5bef68a chore(deps): update dependency happy-dom to v13.2.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build was killed Details
2024-01-19 00:20:24 +00:00
renovate e395d4efdb fix(deps): update dependency vue to v3.4.15
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-18 14:19:39 +00:00
renovate ce54132868 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-18 07:18:54 +00:00
renovate 07d4d1e537 fix(deps): update dependency floating-vue to v5.2.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-17 13:19:31 +00:00
renovate a701b0452e fix(deps): update dependency floating-vue to v5.1.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-17 12:18:43 +00:00
renovate af65efcd27 chore(deps): update dev-dependencies (major) (#3890)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3890
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-17 09:17:35 +00:00
renovate dc2afb9e8d chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-17 07:18:31 +00:00
WofWca e123d4f825 chore(perf): import some modules dynamically (#3179)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3179
Reviewed-by: konrad <k@knt.li>
Co-authored-by: WofWca <wofwca@protonmail.com>
Co-committed-by: WofWca <wofwca@protonmail.com>
2024-01-16 14:24:24 +00:00
renovate b72c963256 fix(deps): update dependency vue to v3.4.14
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2024-01-16 12:10:13 +00:00
renovate 149bbf17eb fix(deps): update dependency floating-vue to v5.1.0
continuous-integration/drone/push Build is passing Details
2024-01-16 12:01:54 +00:00
renovate 265d60cf42 fix(deps): update vueuse to v10.7.2
continuous-integration/drone/push Build is failing Details
2024-01-16 12:01:44 +00:00
renovate 23c9f51e73 fix(deps): update dependency sortablejs to v1.15.2
continuous-integration/drone/push Build is passing Details
2024-01-16 12:01:11 +00:00
renovate ff697d0c7a chore(deps): update dev-dependencies
continuous-integration/drone/push Build is passing Details
2024-01-16 11:50:52 +00:00
renovate 00588cf59f chore(deps): pin node.js (#3895)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #3895
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-16 11:49:00 +00:00
renovate 01089f4f3d fix(deps): update tiptap to v2.1.16 (#3892)
continuous-integration/drone/push Build was killed Details
Reviewed-on: #3892
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-16 11:17:28 +00:00
kolaente a7461d1ddd
chore(deps): increase renovate timeout
continuous-integration/drone/push Build is passing Details
2024-01-16 12:15:04 +01:00
kolaente a451189bb6
fix(test): make date assertion not brittle
continuous-integration/drone/push Build is passing Details
2024-01-16 10:36:29 +01:00
kolaente bf9af27fc3
fix(task): update due date when marking a task done
continuous-integration/drone/push Build is failing Details
2024-01-15 23:33:02 +01:00
kolaente 5619fda0f2
fix(task): bubble date changes from the picker up
continuous-integration/drone/push Build is failing Details
Resolves https://github.com/go-vikunja/frontend/issues/142
2024-01-15 23:23:57 +01:00
kolaente 167953b26b
fix(editor): use higher-contrast colors for links and code
continuous-integration/drone/push Build is passing Details
2024-01-15 22:11:24 +01:00
kolaente 664bf0a5f4
fix(tasks): make sure tasks show up if their parent task is not available in the current view
continuous-integration/drone/push Build is passing Details
Related https://github.com/go-vikunja/frontend/issues/136
Related https://community.vikunja.io/t/subtasks-are-hidden-when-parent-tasks-are-moved/1911
2024-01-15 21:46:47 +01:00
kolaente 5e991f3024
fix: lint
continuous-integration/drone/push Build is passing Details
2024-01-15 16:21:00 +01:00
kolaente 28050d9cd5
fix(labels): make color reset work
continuous-integration/drone/push Build is failing Details
2024-01-15 14:00:08 +01:00
kolaente e94b71d577
fix(editor): list icons
continuous-integration/drone/push Build is passing Details
2024-01-15 13:39:17 +01:00
renovate 336ce217d3 chore(deps): update node.js to v20.11 (#3888)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3888
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-11 11:30:14 +00:00
Frederick [Bot] ce01085951 chore(i18n): update translations via Crowdin
continuous-integration/drone/push Build is passing Details
2024-01-11 00:11:57 +00:00
kolaente 96a6d43a3f
fix(quick add magic): ensure month is removed from task text
continuous-integration/drone/push Build is passing Details
Resolves #3874
2024-01-10 23:54:42 +01:00
kolaente 13d63e34aa
fix(task): don't immediately re-trigger date change when nothing changed
continuous-integration/drone/push Build is passing Details
Resolves https://community.vikunja.io/t/reminder-duplication/76/21?u=kolaente
2024-01-10 23:27:14 +01:00
renovate a8441c72b8 fix(deps): update dependency vue to v3.4.8 (#3886)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3886
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 20:16:50 +00:00
renovate 230fa6ce66 fix(deps): update dependency floating-vue to v5 (#3887)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3887
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 18:18:53 +00:00
renovate 069c491fbd fix(deps): update sentry-javascript monorepo to v7.93.0 (#3859)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3859
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 16:01:02 +00:00
renovate a9eae95d67 chore(deps): update pnpm to v8.14.1 (#3885)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #3885
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2024-01-10 15:57:10 +00:00
26 changed files with 1016 additions and 902 deletions

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@
},
"homepage": "https://vikunja.io/",
"funding": "https://opencollective.com/vikunja",
"packageManager": "pnpm@8.14.0",
"packageManager": "pnpm@8.14.1",
"keywords": [
"todo",
"productivity",
@ -53,45 +53,45 @@
"@infectoone/vue-ganttastic": "2.2.0",
"@intlify/unplugin-vue-i18n": "2.0.0",
"@kyvg/vue3-notification": "3.1.3",
"@sentry/tracing": "7.93.0",
"@sentry/vue": "7.93.0",
"@tiptap/core": "2.1.15",
"@tiptap/extension-blockquote": "2.1.15",
"@tiptap/extension-bold": "2.1.15",
"@tiptap/extension-bullet-list": "2.1.15",
"@tiptap/extension-code": "2.1.15",
"@tiptap/extension-code-block-lowlight": "2.1.15",
"@tiptap/extension-document": "2.1.15",
"@tiptap/extension-dropcursor": "2.1.15",
"@tiptap/extension-gapcursor": "2.1.15",
"@tiptap/extension-hard-break": "2.1.15",
"@tiptap/extension-heading": "2.1.15",
"@tiptap/extension-history": "2.1.15",
"@tiptap/extension-horizontal-rule": "2.1.15",
"@tiptap/extension-image": "2.1.15",
"@tiptap/extension-italic": "2.1.15",
"@tiptap/extension-link": "2.1.15",
"@tiptap/extension-list-item": "2.1.15",
"@tiptap/extension-ordered-list": "2.1.15",
"@tiptap/extension-paragraph": "2.1.15",
"@tiptap/extension-placeholder": "2.1.15",
"@tiptap/extension-strike": "2.1.15",
"@tiptap/extension-table": "2.1.15",
"@tiptap/extension-table-cell": "2.1.15",
"@tiptap/extension-table-header": "2.1.15",
"@tiptap/extension-table-row": "2.1.15",
"@tiptap/extension-task-item": "2.1.15",
"@tiptap/extension-task-list": "2.1.15",
"@tiptap/extension-text": "2.1.15",
"@tiptap/extension-typography": "2.1.15",
"@tiptap/extension-underline": "2.1.15",
"@tiptap/pm": "2.1.15",
"@tiptap/suggestion": "2.1.15",
"@tiptap/vue-3": "2.1.15",
"@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.1",
"@vueuse/router": "10.7.1",
"@vueuse/core": "10.7.2",
"@vueuse/router": "10.7.2",
"axios": "1.6.5",
"blurhash": "2.0.5",
"bulma-css-variables": "0.9.33",
@ -102,7 +102,7 @@
"fast-deep-equal": "3.1.3",
"flatpickr": "4.6.13",
"flexsearch": "0.7.31",
"floating-vue": "2.0.0",
"floating-vue": "5.2.0",
"is-touch-device": "1.0.1",
"klona": "2.0.6",
"lodash.debounce": "4.0.8",
@ -110,10 +110,10 @@
"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.4.7",
"vue": "3.4.15",
"vue-advanced-cropper": "2.8.8",
"vue-flatpickr-component": "11.0.3",
"vue-i18n": "9.9.0",
@ -126,9 +126,9 @@
"@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,35 +136,35 @@
"@types/is-touch-device": "1.0.2",
"@types/lodash.debounce": "4.0.9",
"@types/marked": "5.0.2",
"@types/node": "20.10.8",
"@types/node": "20.11.5",
"@types/postcss-preset-env": "7.7.0",
"@types/sortablejs": "1.15.7",
"@typescript-eslint/eslint-plugin": "6.18.1",
"@typescript-eslint/parser": "6.18.1",
"@typescript-eslint/eslint-plugin": "6.19.0",
"@typescript-eslint/parser": "6.19.0",
"@vitejs/plugin-legacy": "5.2.0",
"@vitejs/plugin-vue": "4.6.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.30001576",
"caniuse-lite": "1.0.30001579",
"css-has-pseudo": "6.0.1",
"csstype": "3.1.3",
"cypress": "13.6.2",
"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",
"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.4",
"rollup": "4.9.5",
"rollup-plugin-visualizer": "5.12.0",
"sass": "1.69.7",
"sass": "1.70.0",
"start-server-and-test": "2.0.3",
"typescript": "5.3.3",
"vite": "5.0.11",
@ -172,7 +172,7 @@
"vite-plugin-pwa": "0.17.4",
"vite-plugin-sentry": "1.3.0",
"vite-svg-loader": "5.1.0",
"vitest": "1.1.3",
"vitest": "1.2.1",
"vue-tsc": "1.8.27",
"wait-on": "7.2.0",
"workbox-cli": "7.0.0"

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,11 @@
"extends": [
"config:js-app"
],
"hostRules": [
{
"timeout": 600000
}
],
"packageRules": [
{
"matchPackageNames": ["happy-dom"],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -288,6 +288,7 @@ async function markAsDone(checked: boolean) {
title: t('task.undo'),
callback: () => undoDone(checked),
}])
updateDueDate()
}
if (checked) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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