Compare commits

..

1 Commits

Author SHA1 Message Date
renovate 87a12b3613 fix(deps): update dependency flexsearch to v0.7.43
renovate/artifacts Artifact file update failure
continuous-integration/drone/pr Build is failing Details
2024-01-30 15:18:51 +00:00
160 changed files with 2885 additions and 4910 deletions

View File

@ -1,5 +1,5 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
require("@rushstack/eslint-patch/modern-module-resolution")
module.exports = {
'root': true,
@ -7,54 +7,52 @@ module.exports = {
'browser': true,
'es2022': true,
'node': true,
'vue/setup-compiler-macros': true,
},
'extends': [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'plugin:vue/vue3-essential',
'@vue/eslint-config-typescript/recommended',
],
'rules': {
'quotes': ['error', 'single'],
'comma-dangle': ['error', 'always-multiline'],
'semi': ['error', 'never'],
'vue/v-on-event-hyphenation': ['warn', 'never', { 'autofix': true }],
'vue/multi-word-component-names': 'off',
// uncategorized rules:
'vue/component-api-style': ['error', ['script-setup']],
'vue/component-name-in-template-casing': ['warn', 'PascalCase'],
'vue/custom-event-name-casing': ['error', 'camelCase'],
'vue/define-macros-order': 'error',
'vue/match-component-file-name': ['error', {
'extensions': ['.js', '.jsx', '.ts', '.tsx', '.vue'],
'shouldMatchCase': true,
}],
'vue/no-boolean-default': ['warn', 'default-false'],
'vue/match-component-import-name': 'error',
'vue/prefer-separate-static-class': 'warn',
'vue/padding-line-between-blocks': 'error',
'vue/next-tick-style': ['error', 'promise'],
'vue/block-lang': [
'vue/html-quotes': [
'error',
{ 'script': { 'lang': 'ts' } },
'double',
],
'quotes': [
'error',
'single',
],
'comma-dangle': [
'error',
'always-multiline',
],
'semi': [
'error',
'never',
],
'vue/no-required-prop-with-default': ['error', { 'autofix': true }],
'vue/no-duplicate-attr-inheritance': 'error',
'vue/no-empty-component-block': 'error',
'vue/html-indent': ['error', 'tab'],
// vue3
'vue/no-ref-object-destructure': 'error',
// see https://segmentfault.com/q/1010000040813116/a-1020000041134455 (original in chinese)
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true }],
'vue/multi-word-component-names': 0,
// disabled until we have support for reactivityTransform
// See https://github.com/vuejs/eslint-plugin-vue/issues/1948
// see also setting in `vite.config`
'vue/no-setup-props-destructure': 0,
},
'parser': 'vue-eslint-parser',
'parserOptions': {
'parser': '@typescript-eslint/parser',
'ecmaVersion': 'latest',
'ecmaVersion': 2022,
'sourceType': 'module',
},
'ignorePatterns': [
'*.test.*',
'cypress/*',
],
'globals': {
'defineProps': 'readonly',
},
}

View File

@ -1,7 +1,3 @@
# This repository was merged with the api and is now archived
You can find the new (old) code over on [vikunja/vikunja](https://kolaente.dev/vikunja/vikunja).
# Web frontend for Vikunja
> The todo app to organize your life.
@ -29,7 +25,7 @@ export DOCKER_BUILDKIT=1
docker build -t vikunja/frontend .
```
Refer to [multi-platform documentation](https://docs.docker.com/build/building/multi-platform/) in order to build for different platforms.
Refer to Refer [to multi-platform documentation](https://docs.docker.com/build/building/multi-platform/) in order to build for the different platform.
## Project setup

View File

@ -562,7 +562,7 @@ describe('Task', () => {
.click()
const today = new Date()
const day = today.toLocaleString('default', {day: 'numeric'})
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`
@ -605,7 +605,7 @@ describe('Task', () => {
.click()
const today = new Date()
const day = today.toLocaleString('default', {day: 'numeric'})
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`

View File

@ -22,7 +22,6 @@
"gantt",
"kanban"
],
"type": "module",
"scripts": {
"serve": "vite",
"preview": "vite preview --port 4173",
@ -30,8 +29,7 @@
"build": "vite build && workbox copyLibraries dist/",
"build:modern-only": "BUILD_MODERN_ONLY=true vite build && workbox copyLibraries dist/",
"build:dev": "vite build --mode development --outDir dist-dev/",
"lint": "eslint 'src/**/*.{js,ts,vue}'",
"lint:fix": "pnpm run lint --fix",
"lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts",
"test:e2e": "start-server-and-test preview http://127.0.0.1:4173 'cypress run --e2e --browser chrome'",
"test:e2e-record": "start-server-and-test preview http://127.0.0.1:4173 'cypress run --e2e --browser chrome --record'",
"test:e2e-dev-dev": "start-server-and-test preview:dev http://127.0.0.1:4173 'cypress open --e2e'",
@ -54,42 +52,42 @@
"@github/hotkey": "3.1.0",
"@infectoone/vue-ganttastic": "2.2.0",
"@intlify/unplugin-vue-i18n": "2.0.0",
"@kyvg/vue3-notification": "3.1.4",
"@sentry/tracing": "7.100.1",
"@sentry/vue": "7.100.1",
"@tiptap/core": "2.2.1",
"@tiptap/extension-blockquote": "2.2.1",
"@tiptap/extension-bold": "2.2.1",
"@tiptap/extension-bullet-list": "2.2.1",
"@tiptap/extension-code": "2.2.1",
"@tiptap/extension-code-block-lowlight": "2.2.1",
"@tiptap/extension-document": "2.2.1",
"@tiptap/extension-dropcursor": "2.2.1",
"@tiptap/extension-gapcursor": "2.2.1",
"@tiptap/extension-hard-break": "2.2.1",
"@tiptap/extension-heading": "2.2.1",
"@tiptap/extension-history": "2.2.1",
"@tiptap/extension-horizontal-rule": "2.2.1",
"@tiptap/extension-image": "2.2.1",
"@tiptap/extension-italic": "2.2.1",
"@tiptap/extension-link": "2.2.1",
"@tiptap/extension-list-item": "2.2.1",
"@tiptap/extension-ordered-list": "2.2.1",
"@tiptap/extension-paragraph": "2.2.1",
"@tiptap/extension-placeholder": "2.2.1",
"@tiptap/extension-strike": "2.2.1",
"@tiptap/extension-table": "2.2.1",
"@tiptap/extension-table-cell": "2.2.1",
"@tiptap/extension-table-header": "2.2.1",
"@tiptap/extension-table-row": "2.2.1",
"@tiptap/extension-task-item": "2.2.1",
"@tiptap/extension-task-list": "2.2.1",
"@tiptap/extension-text": "2.2.1",
"@tiptap/extension-typography": "2.2.1",
"@tiptap/extension-underline": "2.2.1",
"@tiptap/pm": "2.2.1",
"@tiptap/suggestion": "2.2.1",
"@tiptap/vue-3": "2.2.1",
"@kyvg/vue3-notification": "3.1.3",
"@sentry/tracing": "7.98.0",
"@sentry/vue": "7.98.0",
"@tiptap/core": "2.2.0",
"@tiptap/extension-blockquote": "2.2.0",
"@tiptap/extension-bold": "2.2.0",
"@tiptap/extension-bullet-list": "2.2.0",
"@tiptap/extension-code": "2.2.0",
"@tiptap/extension-code-block-lowlight": "2.2.0",
"@tiptap/extension-document": "2.2.0",
"@tiptap/extension-dropcursor": "2.2.0",
"@tiptap/extension-gapcursor": "2.2.0",
"@tiptap/extension-hard-break": "2.2.0",
"@tiptap/extension-heading": "2.2.0",
"@tiptap/extension-history": "2.2.0",
"@tiptap/extension-horizontal-rule": "2.2.0",
"@tiptap/extension-image": "2.2.0",
"@tiptap/extension-italic": "2.2.0",
"@tiptap/extension-link": "2.2.0",
"@tiptap/extension-list-item": "2.2.0",
"@tiptap/extension-ordered-list": "2.2.0",
"@tiptap/extension-paragraph": "2.2.0",
"@tiptap/extension-placeholder": "2.2.0",
"@tiptap/extension-strike": "2.2.0",
"@tiptap/extension-table": "2.2.0",
"@tiptap/extension-table-cell": "2.2.0",
"@tiptap/extension-table-header": "2.2.0",
"@tiptap/extension-table-row": "2.2.0",
"@tiptap/extension-task-item": "2.2.0",
"@tiptap/extension-task-list": "2.2.0",
"@tiptap/extension-text": "2.2.0",
"@tiptap/extension-typography": "2.2.0",
"@tiptap/extension-underline": "2.2.0",
"@tiptap/pm": "2.2.0",
"@tiptap/suggestion": "2.2.0",
"@tiptap/vue-3": "2.2.0",
"@types/is-touch-device": "1.0.2",
"@types/lodash.clonedeep": "4.5.9",
"@vueuse/core": "10.7.2",
@ -103,7 +101,7 @@
"dompurify": "3.0.8",
"fast-deep-equal": "3.1.3",
"flatpickr": "4.6.13",
"flexsearch": "0.7.31",
"flexsearch": "0.7.43",
"floating-vue": "5.2.2",
"is-touch-device": "1.0.1",
"klona": "2.0.6",
@ -114,11 +112,11 @@
"snake-case": "3.0.4",
"sortablejs": "1.15.2",
"tippy.js": "6.3.7",
"ufo": "1.4.0",
"ufo": "1.3.2",
"vue": "3.4.15",
"vue-advanced-cropper": "2.8.8",
"vue-flatpickr-component": "11.0.3",
"vue-i18n": "9.9.1",
"vue-i18n": "9.9.0",
"vue-router": "4.2.5",
"workbox-precaching": "7.0.0",
"zhyswan-vuedraggable": "4.1.3"

View File

@ -30,115 +30,115 @@ dependencies:
version: 2.2.0(dayjs@1.11.10)(vue@3.4.15)
'@intlify/unplugin-vue-i18n':
specifier: 2.0.0
version: 2.0.0(rollup@4.9.6)(vue-i18n@9.9.1)
version: 2.0.0(rollup@4.9.6)(vue-i18n@9.9.0)
'@kyvg/vue3-notification':
specifier: 3.1.4
version: 3.1.4(vue@3.4.15)
specifier: 3.1.3
version: 3.1.3(vue@3.4.15)
'@sentry/tracing':
specifier: 7.100.1
version: 7.100.1
specifier: 7.98.0
version: 7.98.0
'@sentry/vue':
specifier: 7.100.1
version: 7.100.1(vue@3.4.15)
specifier: 7.98.0
version: 7.98.0(vue@3.4.15)
'@tiptap/core':
specifier: 2.2.1
version: 2.2.1(@tiptap/pm@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/extension-blockquote':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-bold':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-bullet-list':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-code':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-code-block-lowlight':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)(@tiptap/extension-code-block@2.1.12)(@tiptap/pm@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)(@tiptap/extension-code-block@2.1.12)(@tiptap/pm@2.2.0)
'@tiptap/extension-document':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-dropcursor':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0)
'@tiptap/extension-gapcursor':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0)
'@tiptap/extension-hard-break':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-heading':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-history':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0)
'@tiptap/extension-horizontal-rule':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0)
'@tiptap/extension-image':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-italic':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-link':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0)
'@tiptap/extension-list-item':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-ordered-list':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-paragraph':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-placeholder':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0)
'@tiptap/extension-strike':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-table':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0)
'@tiptap/extension-table-cell':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-table-header':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-table-row':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-task-item':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0)
'@tiptap/extension-task-list':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-text':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-typography':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/extension-underline':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)
'@tiptap/pm':
specifier: 2.2.1
version: 2.2.1
specifier: 2.2.0
version: 2.2.0
'@tiptap/suggestion':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0)
'@tiptap/vue-3':
specifier: 2.2.1
version: 2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1)(vue@3.4.15)
specifier: 2.2.0
version: 2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0)(vue@3.4.15)
'@types/is-touch-device':
specifier: 1.0.2
version: 1.0.2
@ -212,8 +212,8 @@ dependencies:
specifier: 6.3.7
version: 6.3.7
ufo:
specifier: 1.4.0
version: 1.4.0
specifier: 1.3.2
version: 1.3.2
vue:
specifier: 3.4.15
version: 3.4.15(typescript@5.3.3)
@ -224,8 +224,8 @@ dependencies:
specifier: 11.0.3
version: 11.0.3(vue@3.4.15)
vue-i18n:
specifier: 9.9.1
version: 9.9.1(vue@3.4.15)
specifier: 9.9.0
version: 9.9.0(vue@3.4.15)
vue-router:
specifier: 4.2.5
version: 4.2.5(vue@3.4.15)
@ -3802,7 +3802,7 @@ packages:
- '@vue/composition-api'
dev: false
/@intlify/bundle-utils@7.4.0(vue-i18n@9.9.1):
/@intlify/bundle-utils@7.4.0(vue-i18n@9.9.0):
resolution: {integrity: sha512-AQfjBe2HUxzyN8ignIk3WhhSuVcSuirgzOzkd17nb337rCbI4Gv/t1R60UUyIqFoFdviLb/wLcDUzTD/xXjv9w==}
engines: {node: '>= 14.16'}
peerDependencies:
@ -3814,8 +3814,8 @@ packages:
vue-i18n:
optional: true
dependencies:
'@intlify/message-compiler': 9.9.0
'@intlify/shared': 9.9.0
'@intlify/message-compiler': 9.8.0
'@intlify/shared': 9.8.0
acorn: 8.11.2
escodegen: 2.0.0
estree-walker: 2.0.2
@ -3823,16 +3823,24 @@ packages:
magic-string: 0.30.5
mlly: 1.4.2
source-map-js: 1.0.2
vue-i18n: 9.9.1(vue@3.4.15)
vue-i18n: 9.9.0(vue@3.4.15)
yaml-eslint-parser: 1.2.2
dev: false
/@intlify/core-base@9.9.1:
resolution: {integrity: sha512-qsV15dg7jNX2faBRyKMgZS8UcFJViWEUPLdzZ9UR0kQZpFVeIpc0AG7ZOfeP7pX2T9SQ5jSiorq/tii9nkkafA==}
/@intlify/core-base@9.9.0:
resolution: {integrity: sha512-C7UXPymDIOlMGSNjAhNLtKgzITc/8BjINK5gNKXg8GiWCTwL6n3MWr55czksxn8RM5wTMz0qcLOFT+adtaVQaA==}
engines: {node: '>= 16'}
dependencies:
'@intlify/message-compiler': 9.9.1
'@intlify/shared': 9.9.1
'@intlify/message-compiler': 9.9.0
'@intlify/shared': 9.9.0
dev: false
/@intlify/message-compiler@9.8.0:
resolution: {integrity: sha512-McnYWhcoYmDJvssVu6QGR0shqlkJuL1HHdi5lK7fNqvQqRYaQ4lSLjYmZxwc8tRNMdIe9/KUKfyPxU9M6yCtNQ==}
engines: {node: '>= 16'}
dependencies:
'@intlify/shared': 9.8.0
source-map-js: 1.0.2
dev: false
/@intlify/message-compiler@9.9.0:
@ -3843,14 +3851,6 @@ packages:
source-map-js: 1.0.2
dev: false
/@intlify/message-compiler@9.9.1:
resolution: {integrity: sha512-zTvP6X6HeumHOXuAE1CMMsV6tTX+opKMOxO1OHTCg5N5Sm/F7d8o2jdT6W6L5oHUsJ/vvkGefHIs7Q3hfowmsA==}
engines: {node: '>= 16'}
dependencies:
'@intlify/shared': 9.9.1
source-map-js: 1.0.2
dev: false
/@intlify/shared@9.8.0:
resolution: {integrity: sha512-TmgR0RCLjzrSo+W3wT0ALf9851iFMlVI9EYNGeWvZFUQTAJx0bvfsMlPdgVtV1tDNRiAfhkFsMKu6jtUY1ZLKQ==}
engines: {node: '>= 16'}
@ -3861,12 +3861,7 @@ packages:
engines: {node: '>= 16'}
dev: false
/@intlify/shared@9.9.1:
resolution: {integrity: sha512-b3Pta1nwkz5rGq434v0psHwEwHGy1pYCttfcM22IE//K9owbpkEvFptx9VcuRAxjQdrO2If249cmDDjBu5wMDA==}
engines: {node: '>= 16'}
dev: false
/@intlify/unplugin-vue-i18n@2.0.0(rollup@4.9.6)(vue-i18n@9.9.1):
/@intlify/unplugin-vue-i18n@2.0.0(rollup@4.9.6)(vue-i18n@9.9.0):
resolution: {integrity: sha512-1oKvm92L9l2od2H9wKx2ZvR4tzn7gUtd7bPLI7AWUmm7U9H1iEypndt5d985ypxGsEs0gToDaKTrytbBIJwwSg==}
engines: {node: '>= 14.16'}
peerDependencies:
@ -3881,7 +3876,7 @@ packages:
vue-i18n-bridge:
optional: true
dependencies:
'@intlify/bundle-utils': 7.4.0(vue-i18n@9.9.1)
'@intlify/bundle-utils': 7.4.0(vue-i18n@9.9.0)
'@intlify/shared': 9.8.0
'@rollup/pluginutils': 5.0.2(rollup@4.9.6)
'@vue/compiler-sfc': 3.3.13
@ -3893,7 +3888,7 @@ packages:
picocolors: 1.0.0
source-map-js: 1.0.2
unplugin: 1.1.0
vue-i18n: 9.9.1(vue@3.4.15)
vue-i18n: 9.9.0(vue@3.4.15)
transitivePeerDependencies:
- rollup
- supports-color
@ -3950,8 +3945,8 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/@kyvg/vue3-notification@3.1.4(vue@3.4.15):
resolution: {integrity: sha512-O6W9YdATNRbC0N6815OJ8SE9WXSWK1K8oO9g4I62uoqPT/js9D+Q9aL/lff1O+9BrnvYP4o8pXEBdrllr3O0Vg==}
/@kyvg/vue3-notification@3.1.3(vue@3.4.15):
resolution: {integrity: sha512-z66zE1b37oZodBjiTiaEumBfEyLzpPnDeqpSHlOomYAqeOWwfMGSSUbWc6GZCOFmlZSlfytFLdMJJmEwQUBbRw==}
peerDependencies:
vue: ^3.0.0
dependencies:
@ -4232,45 +4227,45 @@ packages:
resolution: {integrity: sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==}
dev: true
/@sentry-internal/feedback@7.100.1:
resolution: {integrity: sha512-yqcRVnjf+qS+tC4NxOKLJOaSJ+csHmh/dHUzvCTkf5rLsplwXYRnny2r0tqGTQ4tuXMxwgSMKPYwicg81P+xuw==}
/@sentry-internal/feedback@7.98.0:
resolution: {integrity: sha512-t/mATvwkLcQLKRlx8SO5vlUjaadF6sT3lfR0PdWYyBy8qglbMTHDW4KP6JKh1gdzTVQGnwMByy+/4h9gy4AVzw==}
engines: {node: '>=12'}
dependencies:
'@sentry/core': 7.100.1
'@sentry/types': 7.100.1
'@sentry/utils': 7.100.1
'@sentry/core': 7.98.0
'@sentry/types': 7.98.0
'@sentry/utils': 7.98.0
dev: false
/@sentry-internal/replay-canvas@7.100.1:
resolution: {integrity: sha512-TnqxqJGhbFhhYRhTG2WLFer+lVieV7mNGeIxFBiw1L4kuj8KGl+C0sknssKyZSRVJFSahhHIosHJGRMkkD//7g==}
/@sentry-internal/replay-canvas@7.98.0:
resolution: {integrity: sha512-vAR6KIycyazaY9HwxG5UONrPTe8jeKtZr6k04svPC8OvcoI0xF+l1jMEYcarffuzKpZlPfssYb5ChHtKuXCB+Q==}
engines: {node: '>=12'}
dependencies:
'@sentry/core': 7.100.1
'@sentry/replay': 7.100.1
'@sentry/types': 7.100.1
'@sentry/utils': 7.100.1
'@sentry/core': 7.98.0
'@sentry/replay': 7.98.0
'@sentry/types': 7.98.0
'@sentry/utils': 7.98.0
dev: false
/@sentry-internal/tracing@7.100.1:
resolution: {integrity: sha512-+u9RRf5eL3StiyiRyAHZmdkAR7GTSGx4Mt4Lmi5NEtCcWlTGZ1QgW2r8ZbhouVmTiJkjhQgYCyej3cojtazeJg==}
/@sentry-internal/tracing@7.98.0:
resolution: {integrity: sha512-FnhD2uMLIAJvv4XsYPv3qsTTtxrImyLxiZacudJyaWFhxoeVQ8bKKbWJ/Ar68FAwqTtjXMeY5evnEBbRMcQlaA==}
engines: {node: '>=8'}
dependencies:
'@sentry/core': 7.100.1
'@sentry/types': 7.100.1
'@sentry/utils': 7.100.1
'@sentry/core': 7.98.0
'@sentry/types': 7.98.0
'@sentry/utils': 7.98.0
dev: false
/@sentry/browser@7.100.1:
resolution: {integrity: sha512-IxHQ08ixf0bmaWpe4yt1J4UUsOpg02fxax9z3tOQYXw5MSzz5pDXn8M8DFUVJB3wWuyXhHXTub9yD3VIP9fnoA==}
/@sentry/browser@7.98.0:
resolution: {integrity: sha512-/MzTS31N2iM6Qwyh4PSpHihgmkVD5xdfE5qi1mTlwQZz5Yz8t7MdMriX8bEDPlLB8sNxl7+D6/+KUJO8akX0nQ==}
engines: {node: '>=8'}
dependencies:
'@sentry-internal/feedback': 7.100.1
'@sentry-internal/replay-canvas': 7.100.1
'@sentry-internal/tracing': 7.100.1
'@sentry/core': 7.100.1
'@sentry/replay': 7.100.1
'@sentry/types': 7.100.1
'@sentry/utils': 7.100.1
'@sentry-internal/feedback': 7.98.0
'@sentry-internal/replay-canvas': 7.98.0
'@sentry-internal/tracing': 7.98.0
'@sentry/core': 7.98.0
'@sentry/replay': 7.98.0
'@sentry/types': 7.98.0
'@sentry/utils': 7.98.0
dev: false
/@sentry/cli@2.19.1:
@ -4289,53 +4284,53 @@ packages:
- supports-color
dev: true
/@sentry/core@7.100.1:
resolution: {integrity: sha512-f+ItUge/o9AjlveQq0ZUbQauKlPH1FIJbC1TRaYLJ4KNfOdrsh8yZ29RmWv0cFJ/e+FGTr603gWpRPObF5rM8Q==}
/@sentry/core@7.98.0:
resolution: {integrity: sha512-baRUcpCNGyk7cApQHMfqEZJkXdvAKK+z/dVWiMqWc5T5uhzMnPE8/gjP1JZsMtJSQ8g5nHimBdI5TwOyZtxPaA==}
engines: {node: '>=8'}
dependencies:
'@sentry/types': 7.100.1
'@sentry/utils': 7.100.1
'@sentry/types': 7.98.0
'@sentry/utils': 7.98.0
dev: false
/@sentry/replay@7.100.1:
resolution: {integrity: sha512-B1NFjzGEFaqejxBRdUyEzH8ChXc2kfiqlA/W/Lg0aoWIl2/7nuMk+l4ld9gW5F5bIAXDTVd5vYltb1lWEbpr7w==}
/@sentry/replay@7.98.0:
resolution: {integrity: sha512-CQabv/3KnpMkpc2TzIquPu5krpjeMRAaDIO0OpTj5SQeH2RqSq3fVWNZkHa8tLsADxcfLFINxqOg2jd1NxvwxA==}
engines: {node: '>=12'}
dependencies:
'@sentry-internal/tracing': 7.100.1
'@sentry/core': 7.100.1
'@sentry/types': 7.100.1
'@sentry/utils': 7.100.1
'@sentry-internal/tracing': 7.98.0
'@sentry/core': 7.98.0
'@sentry/types': 7.98.0
'@sentry/utils': 7.98.0
dev: false
/@sentry/tracing@7.100.1:
resolution: {integrity: sha512-cmm2yz0qvOcW0RPegCn88X5nwbJdLx3w+Wl8319Kzkivc200e2tXQiDNwJ8kQdUvFsJg7Jz3G+hfZoy3ejtH7Q==}
/@sentry/tracing@7.98.0:
resolution: {integrity: sha512-O5lKcxkeNxoChJI0b/sX3N1TA5TjQuFwH9fMS00xWUFY0ei9HewBZbbx9c9aye2aRIWF2EHMsASZ+GCWGOZTlQ==}
engines: {node: '>=8'}
dependencies:
'@sentry-internal/tracing': 7.100.1
'@sentry-internal/tracing': 7.98.0
dev: false
/@sentry/types@7.100.1:
resolution: {integrity: sha512-fLM+LedHuKzOd8IhXBqaQuym+AA519MGjeczBa5kGakes/BbAsUMwsNfjsKQedp7Kh44RgYF99jwoRPK2oDrXw==}
/@sentry/types@7.98.0:
resolution: {integrity: sha512-pc034ziM0VTETue4bfBcBqTWGy4w0okidtoZJjGVrYAfE95ObZnUGVj/XYIQ3FeCYWIa7NFN2MvdsCS0buwivQ==}
engines: {node: '>=8'}
dev: false
/@sentry/utils@7.100.1:
resolution: {integrity: sha512-Ve6dXr1o6xiBe3VCoJgiutmBKrugryI65EZAbYto5XI+t+PjiLLf9wXtEMF24ZrwImo4Lv3E9Uqza+fWkEbw6A==}
/@sentry/utils@7.98.0:
resolution: {integrity: sha512-0/LY+kpHxItVRY0xPDXPXVsKRb95cXsGSQf8sVMtfSjz++0bLL1U4k7PFz1c5s2/Vk0B8hS6duRrgMv6dMIZDw==}
engines: {node: '>=8'}
dependencies:
'@sentry/types': 7.100.1
'@sentry/types': 7.98.0
dev: false
/@sentry/vue@7.100.1(vue@3.4.15):
resolution: {integrity: sha512-WtLAYmqad9aKp+/Rl6yEXTtnylj/WrPYNwE+EVO34AEP73w2EVeuwAu+o3TGRV7TvCLf4CQ3ZA3KZWCR7uGqIQ==}
/@sentry/vue@7.98.0(vue@3.4.15):
resolution: {integrity: sha512-ucrte6jZVvJFotPrvUDbMyERW35i54tsyMgjnZCBfppwHbUUP5+8CNwyLgsh6fYWSmyCc1ER9AYCmMIEoijBZQ==}
engines: {node: '>=8'}
peerDependencies:
vue: 2.x || 3.x
dependencies:
'@sentry/browser': 7.100.1
'@sentry/core': 7.100.1
'@sentry/types': 7.100.1
'@sentry/utils': 7.100.1
'@sentry/browser': 7.98.0
'@sentry/core': 7.98.0
'@sentry/types': 7.98.0
'@sentry/utils': 7.98.0
vue: 3.4.15(typescript@5.3.3)
dev: false
@ -4378,301 +4373,301 @@ packages:
defer-to-connect: 1.1.3
dev: true
/@tiptap/core@2.2.1(@tiptap/pm@2.2.1):
resolution: {integrity: sha512-9G6N2d5q4M5i/elK61qu28uO5MBDKEbStQnHuVKNe+LVNpb6xZal1AcIilmq1FVeuCUh3kYl3cFH3nBQOXtZUw==}
/@tiptap/core@2.2.0(@tiptap/pm@2.2.0):
resolution: {integrity: sha512-ped7XlQ9k5VyE2xUwyRegn1yVF/CAsaF+riBUBJ9+71/gSo2mCZ+6gQvee+LVN1+rD1qN/vWgKhKNDVaU+VaFg==}
peerDependencies:
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/pm': 2.2.1
'@tiptap/pm': 2.2.0
dev: false
/@tiptap/extension-blockquote@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-KfL7c28ueH8yKTkZiO9LlErAVjZyi5j+Kw42pu06aOrXWNH6TW8CSufR6UB2XZcFX6Sixi2qYpwxs1YNnItmcg==}
/@tiptap/extension-blockquote@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-zINqjH/Kb0qiDxb4ndlruN/g5ZDt+Og2QTyrYGs27xoNmFoHez0Xqi+ID5yFSYBSCGkf0GeNMqW9LjfiCObvww==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-bold@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-MSFbbrUG0hErM9sUmJenGPRBjQV/HfcLf7kqpxduPoxA4r2XOsbUKGSbPEQ3vU3vMi9Obkknt0JLnS5WofIIGw==}
/@tiptap/extension-bold@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-GlrI0FzUSzYhXoYbctcXALbyc22uKfZ0nv1k0qTw8qkKbWsz6qT/rm+rcB2YgugW3r6cu5n5HDQbzbgjapNAEA==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-bubble-menu@2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1):
resolution: {integrity: sha512-FQO1CDS777E34T8jwnYDBz3VmGyVzv3Olj0o01c3fHR7OSnA7urAxx+z9Z4CWqUJhRMV9td0v0wiZjQ4q1DdYw==}
/@tiptap/extension-bubble-menu@2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0):
resolution: {integrity: sha512-ukwFb9wbwA/GkM7wGGv/ScKRBn4QSZkNzObhU6TgDKISjo1b7gGaUdC6gPoAFtQamCOCL8559PJIXXJ4wtL5/w==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/pm': 2.2.1
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/pm': 2.2.0
tippy.js: 6.3.7
dev: false
/@tiptap/extension-bullet-list@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-MvMQRykJUdIocLNmilHO8smbttWwuXUGlnlW+DpIAK7DNih3aHjdFlRbixPqVQLicFMiI5+ixw+EM1ZQr0S59A==}
/@tiptap/extension-bullet-list@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-V/jVw5g1c7EIKo+44Rw1WeQ9NUzXjtecnCclDALXOlhZ3Kz+a6mBOkuMSeoZhlH0sZ7Cq5u4W+64IuDOstA9yg==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-code-block-lowlight@2.2.1(@tiptap/core@2.2.1)(@tiptap/extension-code-block@2.1.12)(@tiptap/pm@2.2.1):
resolution: {integrity: sha512-XrgDfqfuyYQ7xtjCALUbh5Qu4NAD9WXjE8T51C9aL60A6eMcd78QUDYtNEpHEIARqHLC1lWfUN1HhdkG3bFtuA==}
/@tiptap/extension-code-block-lowlight@2.2.0(@tiptap/core@2.2.0)(@tiptap/extension-code-block@2.1.12)(@tiptap/pm@2.2.0):
resolution: {integrity: sha512-s/ZvePEwyovQtoTHFq/TYZU/+rTdWEAhocsnbRD6BVolwFi5NiE9iRtIt2gTL1PVxi6UNK/zxqrtBaQpBffeqg==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/extension-code-block': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/extension-code-block': 2.1.12(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1)
'@tiptap/pm': 2.2.1
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/extension-code-block': 2.1.12(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0)
'@tiptap/pm': 2.2.0
dev: false
/@tiptap/extension-code-block@2.1.12(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1):
/@tiptap/extension-code-block@2.1.12(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0):
resolution: {integrity: sha512-RXtSYCVsnk8D+K80uNZShClfZjvv1EgO42JlXLVGWQdIgaNyuOv/6I/Jdf+ZzhnpsBnHufW+6TJjwP5vJPSPHA==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/pm': 2.2.1
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/pm': 2.2.0
dev: false
/@tiptap/extension-code@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-nRYeiw9MtV6x3klcQd2X3fJ7r8J1Zy+KHTvfJFYsK8SacSVl+tYi8/FsjbmzGmnO3NYUlDzdyPq/woT+cKk20Q==}
/@tiptap/extension-code@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-SqAoJFuwqMzxyaKoULxEgo0e0fJjr/iOLQbnPSQEOuhgVaqRsaBSS3dbxgm94Gt29P7YPZ6IMqLSv/QZQzyjXw==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-document@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-Pb0jSY6wYSKd8fgDotKiQN3mc4dKLWVq0IzYOpBGTLn+37WmyPzWCnA1vWjp81u2XRXvr3gFQBEa+zYXonG//w==}
/@tiptap/extension-document@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-P21yqZP8DQQ03Q84jO4l73XPswCqjCPb318/eSdovF7m7xXcY55HkHtWU5Fvt/KJxhwvG6WWGmvoox9/bDW0DQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-dropcursor@2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1):
resolution: {integrity: sha512-Y9MmqRv0MMzhy8efVvHV1zAwr4oEiodggYOLC8Vav0IubWXqd8mVDCdz3DjEqGOdeR9Qy/zt4ohQlbkRno9twA==}
/@tiptap/extension-dropcursor@2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0):
resolution: {integrity: sha512-7OfGn/EmDp3IN+OyjlFrgeO/GfmBZXMzwW/yWl+42ulhWItqeQ1qmApxOAj1zWes0hbVcULZXIpw3alfTvfPOA==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/pm': 2.2.1
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/pm': 2.2.0
dev: false
/@tiptap/extension-floating-menu@2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1):
resolution: {integrity: sha512-2lQaEIUbexw8yCGDLxNqhCwpZMoclnDtAdObDKCgufLcA+tVSdVirfcx7HJZ4kmRXmUhdG3uvawQVchZJ7FGiw==}
/@tiptap/extension-floating-menu@2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0):
resolution: {integrity: sha512-psvRz/69Q/DnVHABYwMKlZv2C6AuLmcQQKTrEBHZKz7OX6E3pLveRVIjaetFg8jci8ZeNMPupKmeLzBVuN8U2A==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/pm': 2.2.1
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/pm': 2.2.0
tippy.js: 6.3.7
dev: false
/@tiptap/extension-gapcursor@2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1):
resolution: {integrity: sha512-/OCQDwa1/LwFVEFWCkU4dnUt4pGt1IJKOEzT2JETrRQB4VzP1flIpwPAy5tJ3i2abkCZtnEGOJ5ZsXC01LgOxg==}
/@tiptap/extension-gapcursor@2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0):
resolution: {integrity: sha512-FsIoLA2xC1tUCK2cw5jWOKlwYNZgex3puMRwQaZjbph2oz8Jel8SRAzAwsfoi4JkaN9TpNlRP1i00xzGGaezow==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/pm': 2.2.1
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/pm': 2.2.0
dev: false
/@tiptap/extension-hard-break@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-wKRujmJlaBOv+PEnaUL2C9fEjiq1YDsm/SbHzAPYn/+8CRHOLrcJpUAq9rUo7u6+bEBD2PxbN1pPNrfS3QtPLw==}
/@tiptap/extension-hard-break@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-VptjsgvYOK6EcErn7GdEZZ5CjRkC4O6dsz2cRFA/DIY1IecB628X81n4G6cCyVsb+D98cYnsY44D3Qo+70Xkzw==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-heading@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-BI14qTOWTcabvobSo8QBAXQqmz4vQ9HjInK9btKEAJ027BHFk08n6YoovQDcTH5Z2nvwPN4N3RW0JupmgwflAQ==}
/@tiptap/extension-heading@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-4VwRtGDhRhUtP/c8BB6pPMS6g8PRv5cs+pYxCp466en5awSBhM4AN2cxDD6ruXP4YvNTDprky9H0IKjhs6Ym4Q==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-history@2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1):
resolution: {integrity: sha512-MZyIKWgNLzsEysyzWwsUE705+x4GRuLluS5cWGzZ/CKjSEUO152DVlM50MPpKqH+o1SW3cR7RUvDPJlpM7Lymw==}
/@tiptap/extension-history@2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0):
resolution: {integrity: sha512-djHQxD5KP4EI3U6cri0/wcJxyMspU1B6+UVXL1G7867JiV9mvAw/HUoZsHTOsn+kajTi8szFfl2exa6dCYVrmA==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/pm': 2.2.1
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/pm': 2.2.0
dev: false
/@tiptap/extension-horizontal-rule@2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1):
resolution: {integrity: sha512-M6mi6CPMeO/1i/0qqEIVbObB3VDT6HhO4cxvjiaWxRXsU+79kdvx33Mk6GHKuXZizAYN1qxeM7byAtFiRu/aIw==}
/@tiptap/extension-horizontal-rule@2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0):
resolution: {integrity: sha512-AmaMBZwCEBH2eoy9Qt8Z+fiP6olSelz/cODz8/8XiXXi8ydY6oHW2s4pmrEYZH0un9dQAGWMP/LGUwyQZof1GQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/pm': 2.2.1
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/pm': 2.2.0
dev: false
/@tiptap/extension-image@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-Rbj3dwcAfmLmKgv6ymfLUWlpb5xS3W4Bl/xC9EKbTFvRG+OHJyDjKxUipjNlaycuhgO01i2N9Jftclg4ZVed+g==}
/@tiptap/extension-image@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-xP+V+n6GQ/iHbDzX7wKy++klTUFQsvrtOtS2cWJCmUUH9qHJ2vHoaLEKAaroIaAaq/QMroCuEItpbevC3hbnpg==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-italic@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-x3cWWgup7SZIq8ugfJ8IFpvOROwU0KuAZFJlSk9eL9Qszff2ZG8kPUuQ8HklSdmoafSluv0mUhDSFTc52E/Yzg==}
/@tiptap/extension-italic@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-eOesosmbf8mXCJ8E58PnuhO5gtDpviCTpi3ZaGn1yP0gLRV3wlo8wpCbxlGXLStgFT0tejN26MGbEHCJHkRkFQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-link@2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1):
resolution: {integrity: sha512-3oh+tRFGtj3Scv3i6FBJOakX650NBI+cmemJxggaiH8PPw3+q+eZneqDQHqINcgel5/HQXFdXWSpfYsZi7EDoQ==}
/@tiptap/extension-link@2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0):
resolution: {integrity: sha512-1evYv5Fjod47kobd/0RsHYyCFWrkU5IYZSIBC7bAGsnzG4fId2O5SEyn9SPsMYOVtEL2EIhiqb0i+wcwXf6jOQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/pm': 2.2.1
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/pm': 2.2.0
linkifyjs: 4.1.1
dev: false
/@tiptap/extension-list-item@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-6seOMXbji+ptW9OiOig8BVBxnGZhHgWEU6tmsMxsf61dt8b1CElf04oTbvn1hzkCjQQXJbsztU87Q7FLTfLE0w==}
/@tiptap/extension-list-item@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-st5S4z2+IXsaX9Otm+4y1NZhE/yLtfFELn5VIUX69PwnNThaN2/ioBXkO1o2ZdLex++D0oMaY5ILIW/PhCWP8Q==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-ordered-list@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-6OcdBALgYnj/TKSV+ouT202U1ydJGenGL9ofhpdogC1scE0oMaR25f1T9k7RMUGAv+7FNIcVZp2+bf1SLCTX1A==}
/@tiptap/extension-ordered-list@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-LR+RQdv8yEqSYkTBGYuh099wr4r0QIosxfGe/ZlshwDJ4qFKVhyWSZ8qyaEiufmXDadTasj7WQvlbpVAhrVTBQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-paragraph@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-SaSnWml4DTzG9kTEq/diL6XQGSgNaZJPOIGTPL2/1idHrx4/JrZ87VIbChJwVqbDuAvAxbidAqxKJuYObSzhhg==}
/@tiptap/extension-paragraph@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-niuvuBEkhz9gnQvFHbxs5z044bpDXRH9zz8QW2bA8+IDSxWHfnVmSZ3AZsed7OJ4EK1AcgGxy+gFOpAcZ73XTw==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-placeholder@2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1):
resolution: {integrity: sha512-QEUEhzvUOLRjdvzm45CGNqdzqNalKjYgnkmdiNr7HPsmXJ+Teoe/dP7HTo2ssGRqkziE9OJrDRu12kCr+H73MQ==}
/@tiptap/extension-placeholder@2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0):
resolution: {integrity: sha512-YAvq4VwjI9SitRHHQBoI2YHvKlmXvLag+z336HYyEiHDkoadek6zWLIb+RY/D0lAPBMBzgN+LoknYhC1VdgdYA==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/pm': 2.2.1
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/pm': 2.2.0
dev: false
/@tiptap/extension-strike@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-4XntI9CLteDeu1ZBWqTygiuut2mJbS5E6SxDD0LZ6EtXM1LdTkh5xhDdviErsqlAe1PUtrFcGUAfdyMOdp26uQ==}
/@tiptap/extension-strike@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-AMVC94mWNCAXLyZdkB5KqLai9FMUaQDkWAZSD1DKCRq2OJeA71nW9G1eDPa2TzQezl2IVTW9mU4m37hcsTmRfg==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-table-cell@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-AeoIAuE+WLh69r/irk9lXo0wVCYpV1CI7aTcrWRP8P4t+wELfBPq/2dT1ETZ1dFWg1JXdq4FFtZ0Cw8lqlJnFQ==}
/@tiptap/extension-table-cell@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-wWLJnQdxF7tnS0oyZGWlUj3k+wf0R2fbq1n0n0EbnOmZV9CWhYwmKnUbdMEsW/HCYm1lgc94TAYGwaHkqQvHWQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-table-header@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-OQR9hNFSTGQFnoWf4SBIsnjgim8yWgMHT/SfCKyFWvgJnSGJS0VHVRItML1sFs8U4xJccfeNK8AiaaQUbMRiFg==}
/@tiptap/extension-table-header@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-U2XQcXinMNlx4fhRI7mivx8mFclajR7q3+gU1sFwGaRuyufOUM/1LwoiuzHK1VqMQWkWq8u+XEqtw9K6BtmyNw==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-table-row@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-3eFeuxOBzyEVcoo8CyFjJHPY4V1doZDxCdVXuFVoDcukQV12aJaV9TCeo3XjjAvs6Sqmm5G49M7JyIlCYIx20g==}
/@tiptap/extension-table-row@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-sR0MWUz5hy6QZsaaI/v4+wC8V8IKRZOZvl7PMhKAJVnAGr5nF34V8h/E9kWGDDyYw2mKi5dSf1E/cEZVAwdlVQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-table@2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1):
resolution: {integrity: sha512-5CovUPpa1yC0JEkuQ80B1r/i2SP/NjMCG+1AMCxQJg/a+rjP3DWK+FubhE67455YvwjxdllCvlZyruludCWXxQ==}
/@tiptap/extension-table@2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0):
resolution: {integrity: sha512-Kv9QhD3DXroygoQ9yY0fiZFsEDyiM10kwRrBdv05EE3+QJhkv+NaX6OgRxiOa6/AyxC1/SyTjjeKz0IgA/XZgA==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/pm': 2.2.1
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/pm': 2.2.0
dev: false
/@tiptap/extension-task-item@2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1):
resolution: {integrity: sha512-gu8XJ6OCw1VZNe/Q/bTzk214d9bq5B/zN4BR/l+2ogbb5eXi00yEiatqMX40Ee4HzVqVGHJhclDHYRK9k+oAIw==}
/@tiptap/extension-task-item@2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0):
resolution: {integrity: sha512-5Tty8sEE7fS6Ymdpd81Y+ci9FS8bctvPfqF2CPjgrbd4TesXGmPoUz6XBm2Pfh2MBBQhUIy0+3au5mDzcVq2SA==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/pm': 2.2.1
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/pm': 2.2.0
dev: false
/@tiptap/extension-task-list@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-hE/h7a2fYm0olWax7ywm+K10L4whyWZdlwDSTHiuE44++o+ANn4AcV/izz8/qFBbPUnf9qG0QZituzLQlTTenw==}
/@tiptap/extension-task-list@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-+W4uVup/v/P67sSJ8bAbbR7lCKZl+Jrm66lIV4etGjhO9Hs2ZnV9KrAjTHh8SbZZSNybDTOOaV74Wo73KqoVUQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-text@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-1zjPuGyhLIODsYpCYeRSUgzfzdzk5uSo8PsV/WaF75ouilJYE3QvMQegivkuNmPBLz0nukPkOcXfQUI/D8wNOA==}
/@tiptap/extension-text@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-h3lJRUZaUBisqUSQDEO+NU4SgKW7rj/vvbsbML8klWdEhg8U9btdv4eZgoJxbsqOdpUc6Cy6dBhre4myWI8Y2w==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-typography@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-V/ULb2aoyNo4k4a890N8+WiGDAAF8iJZ7XSGXzuXLUyabVGhdDO4xRymZpLROHjmreYRbfMzuTOTvOWi30DOvw==}
/@tiptap/extension-typography@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-te9JQqqjZbaEBPeNJeEDiy9HU/lvlG0lCq9Zz8EnHIJG69A3rXHpwLAbGY/WiQNk/MCx6HjBeVyjRCeHvo56Cw==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/extension-underline@2.2.1(@tiptap/core@2.2.1):
resolution: {integrity: sha512-lGPoFFzVhqApG2Q4dIsxzMVbdhJw6bLqFHN9g1Ei53NZ3uGMJbaHoB9bbRGWPkuG+K6Y5shf5fByXbmSjBvBuA==}
/@tiptap/extension-underline@2.2.0(@tiptap/core@2.2.0):
resolution: {integrity: sha512-y+D9gUWa/sVeCftZBMCEpcD1tD4OgNXfjsS7/qxv6ge9t0HCRhAJfHWm6rX8P7QybjacinZuun/T6CudRdGbMg==}
peerDependencies:
'@tiptap/core': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
dev: false
/@tiptap/pm@2.2.1:
resolution: {integrity: sha512-jfbrvUlDZ5t6zgk7pXT6GtHX7v48PL6K9TfDEzROywJS2iXSt/0iHUi2Ap8u8hvfv4rBDO7m7LVLS0+rRa5Cyg==}
/@tiptap/pm@2.2.0:
resolution: {integrity: sha512-CL/ys9rvUgYcRHyeQFuIQdw09+0LUgKAfYWzCr6Pu4DDdbRooiex/a9M29imnRMEgS9SwuHN2v17fKHH0w65Hg==}
dependencies:
prosemirror-changeset: 2.2.1
prosemirror-collab: 1.3.1
@ -4694,27 +4689,27 @@ packages:
prosemirror-view: 1.32.7
dev: false
/@tiptap/suggestion@2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1):
resolution: {integrity: sha512-DIT4zR5CoP0Q5n9BJj68EqmbFGzdyDgDon8yfX1NkynW8PtDrcfGhe5/31tcH6YuM6ZijqNV3fdCt+LPrVr/Nw==}
/@tiptap/suggestion@2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0):
resolution: {integrity: sha512-xcjGEVgRB0tx21LdNTr5F/HEKNlFuq9cnJlVGDLlNP43ixXM6aODAeY3VvEZ3XtEtLpHsiCVrinrjRNtpUf+eQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/pm': 2.2.1
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/pm': 2.2.0
dev: false
/@tiptap/vue-3@2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1)(vue@3.4.15):
resolution: {integrity: sha512-vRYTPnpKJBrpQEjj+7WDx7AKcjNskOlHMEfgCPlwFMG6o1dwbDDtXWj9U53SyiZfitRkudb7WAbX0RyleBjdIQ==}
/@tiptap/vue-3@2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0)(vue@3.4.15):
resolution: {integrity: sha512-S61ENjHm8Y0agwGFw+BZBVuZpL7/rPtMQCOL0eaDrJbqLi6nwFhhWzZ71zis0EgU310NwPCQ+j3XHD5hj1R3gQ==}
peerDependencies:
'@tiptap/core': ^2.0.0
'@tiptap/pm': ^2.0.0
vue: ^3.0.0
dependencies:
'@tiptap/core': 2.2.1(@tiptap/pm@2.2.1)
'@tiptap/extension-bubble-menu': 2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1)
'@tiptap/extension-floating-menu': 2.2.1(@tiptap/core@2.2.1)(@tiptap/pm@2.2.1)
'@tiptap/pm': 2.2.1
'@tiptap/core': 2.2.0(@tiptap/pm@2.2.0)
'@tiptap/extension-bubble-menu': 2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0)
'@tiptap/extension-floating-menu': 2.2.0(@tiptap/core@2.2.0)(@tiptap/pm@2.2.0)
'@tiptap/pm': 2.2.0
vue: 3.4.15(typescript@5.3.3)
dev: false
@ -8686,7 +8681,7 @@ packages:
acorn: 8.11.2
pathe: 1.1.1
pkg-types: 1.0.3
ufo: 1.4.0
ufo: 1.3.2
/mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
@ -10830,8 +10825,8 @@ packages:
resolution: {integrity: sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==}
dev: false
/ufo@1.4.0:
resolution: {integrity: sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==}
/ufo@1.3.2:
resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==}
/unbox-primitive@1.0.1:
resolution: {integrity: sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==}
@ -11275,14 +11270,14 @@ packages:
vue: 3.4.15(typescript@5.3.3)
dev: false
/vue-i18n@9.9.1(vue@3.4.15):
resolution: {integrity: sha512-xyQ4VspLdNSPTKBFBPWa1tvtj+9HuockZwgFeD2OhxxXuC2CWeNvV4seu2o9+vbQOyQbhAM5Ez56oxUrrnTWdw==}
/vue-i18n@9.9.0(vue@3.4.15):
resolution: {integrity: sha512-xQ5SxszUAqK5n84N+uUyHH/PiQl9xZ24FOxyAaNonmOQgXeN+rD9z/6DStOpOxNFQn4Cgcquot05gZc+CdOujA==}
engines: {node: '>= 16'}
peerDependencies:
vue: ^3.0.0
dependencies:
'@intlify/core-base': 9.9.1
'@intlify/shared': 9.9.1
'@intlify/core-base': 9.9.0
'@intlify/shared': 9.9.0
'@vue/devtools-api': 6.5.0
vue: 3.4.15(typescript@5.3.3)
dev: false

View File

@ -1,23 +1,23 @@
<template>
<Ready>
<ready>
<template v-if="authUser">
<TheNavigation />
<ContentAuth />
<TheNavigation/>
<content-auth/>
</template>
<ContentLinkShare v-else-if="authLinkShare" />
<NoAuthWrapper v-else>
<router-view />
</NoAuthWrapper>
<content-link-share v-else-if="authLinkShare"/>
<no-auth-wrapper v-else>
<router-view/>
</no-auth-wrapper>
<KeyboardShortcuts v-if="keyboardShortcutsActive" />
<keyboard-shortcuts v-if="keyboardShortcutsActive"/>
<Teleport to="body">
<AddToHomeScreen />
<UpdateNotification />
<Notification />
<DemoMode />
<AddToHomeScreen/>
<UpdateNotification/>
<Notification/>
<DemoMode/>
</Teleport>
</Ready>
</ready>
</template>
<script lang="ts" setup>

View File

@ -21,16 +21,10 @@ const state = reactive({
</script>
<template>
<Story
:setup-app="setupApp"
:layout="{ type: 'grid', width: '200px' }"
>
<Story :setup-app="setupApp" :layout="{ type: 'grid', width: '200px' }">
<Variant title="custom">
<template #controls>
<HstCheckbox
v-model="state.disabled"
title="Disabled"
/>
<HstCheckbox v-model="state.disabled" title="Disabled" />
</template>
<BaseButton :disabled="state.disabled">
Hello!

View File

@ -6,39 +6,38 @@
<template>
<div
v-if="disabled === true && (to !== undefined || href !== undefined)"
ref="button"
class="base-button"
:aria-disabled="disabled || undefined"
ref="button"
>
<slot />
<slot/>
</div>
<router-link
v-else-if="to !== undefined"
ref="button"
:to="to"
class="base-button"
>
<slot />
</router-link>
<a
v-else-if="href !== undefined"
ref="button"
>
<slot/>
</router-link>
<a v-else-if="href !== undefined"
class="base-button"
:href="href"
rel="noreferrer noopener nofollow"
target="_blank"
ref="button"
>
<slot />
<slot/>
</a>
<button
v-else
ref="button"
:type="type"
class="base-button base-button--type-button"
:disabled="disabled || undefined"
ref="button"
@click="(event: MouseEvent) => emit('click', event)"
>
<slot />
<slot/>
</button>
</template>

View File

@ -1,26 +1,17 @@
<template>
<div
v-cy="'checkbox'"
class="base-checkbox"
>
<div class="base-checkbox" v-cy="'checkbox'">
<input
:id="checkboxId"
type="checkbox"
:id="checkboxId"
class="is-sr-only"
:checked="modelValue"
:disabled="disabled || undefined"
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
>
:disabled="disabled || undefined"
/>
<slot
name="label"
:checkbox-id="checkboxId"
>
<label
:for="checkboxId"
class="base-checkbox__label"
>
<slot />
<slot name="label" :checkboxId="checkboxId">
<label :for="checkboxId" class="base-checkbox__label">
<slot/>
</label>
</slot>
</div>

View File

@ -1,30 +1,27 @@
<template>
<transition
name="expandable-slide"
@beforeEnter="beforeEnter"
@enter="enter"
@afterEnter="afterEnter"
@enterCancelled="enterCancelled"
@beforeLeave="beforeLeave"
@leave="leave"
@afterLeave="afterLeave"
@leaveCancelled="leaveCancelled"
>
<div
v-if="initialHeight"
class="expandable-initial-height"
:style="{ maxHeight: `${initialHeight}px` }"
:class="{ 'expandable-initial-height--expanded': open }"
>
<slot />
</div>
<div
v-else-if="open"
class="expandable"
>
<slot />
</div>
</transition>
<transition
name="expandable-slide"
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="leaveCancelled"
>
<div
v-if="initialHeight"
class="expandable-initial-height"
:style="{ maxHeight: `${initialHeight}px` }"
:class="{ 'expandable-initial-height--expanded': open }"
>
<slot />
</div>
<div v-else-if="open" class="expandable">
<slot />
</div>
</transition>
</template>
<script setup lang="ts">

View File

@ -3,9 +3,9 @@ import datemathHelp from './datemathHelp.vue'
</script>
<template>
<Story>
<Variant title="Default">
<datemathHelp />
</Variant>
</Story>
<Story>
<Variant title="Default">
<datemathHelp />
</Variant>
</Story>
</template>

View File

@ -7,29 +7,21 @@
{{ $t('input.datemathHelp.intro') }}
</p>
<p>
<i18n-t
keypath="input.datemathHelp.expression"
scope="global"
>
<i18n-t keypath="input.datemathHelp.expression" scope="global">
<code>now</code>
<code>||</code>
</i18n-t>
</p>
<p>
<i18n-t
keypath="input.datemathHelp.similar"
scope="global"
>
<i18n-t keypath="input.datemathHelp.similar" scope="global">
<BaseButton
href="https://grafana.com/docs/grafana/latest/dashboards/time-range-controls/"
target="_blank"
>
target="_blank">
Grafana
</BaseButton>
<BaseButton
href="https://www.elastic.co/guide/en/elasticsearch/reference/7.3/common-options.html#date-math"
target="_blank"
>
target="_blank">
Elasticsearch
</BaseButton>
</i18n-t>
@ -43,79 +35,76 @@
<h3>{{ $t('input.datemathHelp.supportedUnits') }}</h3>
<table class="table">
<tbody>
<tr>
<td><code>s</code></td>
<td>{{ $t('input.datemathHelp.units.seconds') }}</td>
</tr>
<tr>
<td><code>m</code></td>
<td>{{ $t('input.datemathHelp.units.minutes') }}</td>
</tr>
<tr>
<td><code>h</code></td>
<td>{{ $t('input.datemathHelp.units.hours') }}</td>
</tr>
<tr>
<td><code>H</code></td>
<td>{{ $t('input.datemathHelp.units.hours') }}</td>
</tr>
<tr>
<td><code>d</code></td>
<td>{{ $t('input.datemathHelp.units.days') }}</td>
</tr>
<tr>
<td><code>w</code></td>
<td>{{ $t('input.datemathHelp.units.weeks') }}</td>
</tr>
<tr>
<td><code>M</code></td>
<td>{{ $t('input.datemathHelp.units.months') }}</td>
</tr>
<tr>
<td><code>y</code></td>
<td>{{ $t('input.datemathHelp.units.years') }}</td>
</tr>
<tr>
<td><code>s</code></td>
<td>{{ $t('input.datemathHelp.units.seconds') }}</td>
</tr>
<tr>
<td><code>m</code></td>
<td>{{ $t('input.datemathHelp.units.minutes') }}</td>
</tr>
<tr>
<td><code>h</code></td>
<td>{{ $t('input.datemathHelp.units.hours') }}</td>
</tr>
<tr>
<td><code>H</code></td>
<td>{{ $t('input.datemathHelp.units.hours') }}</td>
</tr>
<tr>
<td><code>d</code></td>
<td>{{ $t('input.datemathHelp.units.days') }}</td>
</tr>
<tr>
<td><code>w</code></td>
<td>{{ $t('input.datemathHelp.units.weeks') }}</td>
</tr>
<tr>
<td><code>M</code></td>
<td>{{ $t('input.datemathHelp.units.months') }}</td>
</tr>
<tr>
<td><code>y</code></td>
<td>{{ $t('input.datemathHelp.units.years') }}</td>
</tr>
</tbody>
</table>
<h3>{{ $t('input.datemathHelp.someExamples') }}</h3>
<table class="table">
<tbody>
<tr>
<td><code>now</code></td>
<td>{{ $t('input.datemathHelp.examples.now') }}</td>
</tr>
<tr>
<td><code>now+24h</code></td>
<td>{{ $t('input.datemathHelp.examples.in24h') }}</td>
</tr>
<tr>
<td><code>now/d</code></td>
<td>{{ $t('input.datemathHelp.examples.today') }}</td>
</tr>
<tr>
<td><code>now/w</code></td>
<td>{{ $t('input.datemathHelp.examples.beginningOfThisWeek') }}</td>
</tr>
<tr>
<td><code>now/w+1w</code></td>
<td>{{ $t('input.datemathHelp.examples.endOfThisWeek') }}</td>
</tr>
<tr>
<td><code>now+30d</code></td>
<td>{{ $t('input.datemathHelp.examples.in30Days') }}</td>
</tr>
<tr>
<td><code>{{ exampleDate }}||+1M/d</code></td>
<td>
<i18n-t
keypath="input.datemathHelp.examples.datePlusMonth"
scope="global"
>
<strong>{{ exampleDate }}</strong>
</i18n-t>
</td>
</tr>
<tr>
<td><code>now</code></td>
<td>{{ $t('input.datemathHelp.examples.now') }}</td>
</tr>
<tr>
<td><code>now+24h</code></td>
<td>{{ $t('input.datemathHelp.examples.in24h') }}</td>
</tr>
<tr>
<td><code>now/d</code></td>
<td>{{ $t('input.datemathHelp.examples.today') }}</td>
</tr>
<tr>
<td><code>now/w</code></td>
<td>{{ $t('input.datemathHelp.examples.beginningOfThisWeek') }}</td>
</tr>
<tr>
<td><code>now/w+1w</code></td>
<td>{{ $t('input.datemathHelp.examples.endOfThisWeek') }}</td>
</tr>
<tr>
<td><code>now+30d</code></td>
<td>{{ $t('input.datemathHelp.examples.in30Days') }}</td>
</tr>
<tr>
<td><code>{{ exampleDate }}||+1M/d</code></td>
<td>
<i18n-t keypath="input.datemathHelp.examples.datePlusMonth" scope="global">
<strong>{{ exampleDate }}</strong>
</i18n-t>
</td>
</tr>
</tbody>
</table>
</card>

View File

@ -1,31 +1,20 @@
<template>
<div class="datepicker-with-range-container">
<Popup>
<popup>
<template #trigger="{toggle}">
<slot
name="trigger"
:toggle="toggle"
:button-text="buttonText"
/>
<slot name="trigger" :toggle="toggle" :buttonText="buttonText"></slot>
</template>
<template #content="{isOpen}">
<div
class="datepicker-with-range"
:class="{'is-open': isOpen}"
>
<div class="datepicker-with-range" :class="{'is-open': isOpen}">
<div class="selections">
<BaseButton
:class="{'is-active': customRangeActive}"
@click="setDateRange(null)"
>
<BaseButton @click="setDateRange(null)" :class="{'is-active': customRangeActive}">
{{ $t('misc.custom') }}
</BaseButton>
<BaseButton
v-for="(value, text) in DATE_RANGES"
:key="text"
:class="{'is-active': from === value[0] && to === value[1]}"
@click="setDateRange(value)"
>
:class="{'is-active': from === value[0] && to === value[1]}">
{{ $t(`input.datepickerRange.ranges.${text}`) }}
</BaseButton>
</div>
@ -34,18 +23,10 @@
{{ $t('input.datepickerRange.from') }}
<div class="field has-addons">
<div class="control is-fullwidth">
<input
v-model="from"
class="input"
type="text"
>
<input class="input" type="text" v-model="from"/>
</div>
<div class="control">
<x-button
icon="calendar"
variant="secondary"
data-toggle
/>
<x-button icon="calendar" variant="secondary" data-toggle/>
</div>
</div>
</label>
@ -53,49 +34,38 @@
{{ $t('input.datepickerRange.to') }}
<div class="field has-addons">
<div class="control is-fullwidth">
<input
v-model="to"
class="input"
type="text"
>
<input class="input" type="text" v-model="to"/>
</div>
<div class="control">
<x-button
icon="calendar"
variant="secondary"
data-toggle
/>
<x-button icon="calendar" variant="secondary" data-toggle/>
</div>
</div>
</label>
<flat-pickr
v-model="flatpickrRange"
:config="flatPickerConfig"
v-model="flatpickrRange"
/>
<p>
{{ $t('input.datemathHelp.canuse') }}
<BaseButton
class="has-text-primary"
@click="showHowItWorks = true"
>
<BaseButton class="has-text-primary" @click="showHowItWorks = true">
{{ $t('input.datemathHelp.learnhow') }}
</BaseButton>
</p>
<modal
:enabled="showHowItWorks"
@close="() => showHowItWorks = false"
transition-name="fade"
:overflow="true"
variant="hint-modal"
@close="() => showHowItWorks = false"
>
<DatemathHelp />
<DatemathHelp/>
</modal>
</div>
</div>
</template>
</Popup>
</popup>
</div>
</template>
@ -113,16 +83,15 @@ import BaseButton from '@/components/base/BaseButton.vue'
import DatemathHelp from '@/components/date/datemathHelp.vue'
import { getFlatpickrLanguage } from '@/helpers/flatpickrLanguage'
const {t} = useI18n({useScope: 'global'})
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: {
required: false,
},
})
const emit = defineEmits(['update:modelValue'])
const {t} = useI18n({useScope: 'global'})
const flatPickerConfig = computed(() => ({
altFormat: t('date.altFormatLong'),
altInput: true,

View File

@ -4,18 +4,12 @@
class="add-to-home-screen"
:class="{'has-update-available': hasUpdateAvailable}"
>
<icon
icon="arrow-up-from-bracket"
class="add-icon"
/>
<icon icon="arrow-up-from-bracket" class="add-icon"/>
<p>
{{ $t('home.addToHomeScreen') }}
</p>
<BaseButton
class="hide-button"
@click="() => hideMessage = true"
>
<icon icon="x" />
<BaseButton @click="() => hideMessage = true" class="hide-button">
<icon icon="x"/>
</BaseButton>
</div>
</template>

View File

@ -17,11 +17,8 @@ const enabled = computed(() => configStore.demoModeEnabled && !hide.value)
{{ $t('demo.title') }}
<strong class="is-uppercase">{{ $t('demo.everythingWillBeDeleted') }}</strong>
</p>
<BaseButton
class="hide-button"
@click="() => hide = true"
>
<icon icon="times" />
<BaseButton @click="() => hide = true" class="hide-button">
<icon icon="times"/>
</BaseButton>
</div>
</template>

View File

@ -15,17 +15,8 @@ const CustomLogo = computed(() => window.CUSTOM_LOGO_URL)
<template>
<div>
<Logo
v-if="!CustomLogo"
alt="Vikunja"
class="logo"
/>
<img
v-show="CustomLogo"
:src="CustomLogo"
alt="Vikunja"
class="logo"
>
<Logo v-if="!CustomLogo" alt="Vikunja" class="logo" />
<img v-show="CustomLogo" :src="CustomLogo" alt="Vikunja" class="logo" />
</div>
</template>

View File

@ -1,11 +1,11 @@
<template>
<BaseButton
v-shortcut="'Mod+e'"
class="menu-show-button"
:title="$t('keyboardShortcuts.toggleMenu')"
:aria-label="menuActive ? $t('misc.hideMenu') : $t('misc.showMenu')"
@click="baseStore.toggleMenu()"
@shortkey="() => baseStore.toggleMenu()"
v-shortcut="'Mod+e'"
:title="$t('keyboardShortcuts.toggleMenu')"
:aria-label="menuActive ? $t('misc.hideMenu') : $t('misc.showMenu')"
/>
</template>

View File

@ -1,11 +1,7 @@
<template>
<BaseButton
class="menu-bottom-link"
:href="poweredByUrl"
target="_blank"
>
{{ $t('misc.poweredBy') }}
</BaseButton>
<BaseButton class="menu-bottom-link" :href="poweredByUrl" target="_blank">
{{ $t('misc.poweredBy') }}
</BaseButton>
</template>
<script setup lang="ts">

View File

@ -2,8 +2,10 @@
<draggable
v-model="availableProjects"
animation="100"
ghost-class="ghost"
ghostClass="ghost"
group="projects"
@start="() => drag = true"
@end="saveProjectPosition"
handle=".handle"
tag="menu"
item-key="id"
@ -17,8 +19,6 @@
{ 'dragging-disabled': !canEditOrder }
],
}"
@start="() => drag = true"
@end="saveProjectPosition"
>
<template #item="{element: project}">
<ProjectsNavigationItem

View File

@ -6,13 +6,10 @@
<div>
<BaseButton
v-if="canCollapse && childProjects?.length > 0"
class="collapse-project-button"
@click="childProjectsOpen = !childProjectsOpen"
class="collapse-project-button"
>
<icon
icon="chevron-down"
:class="{ 'project-is-collapsed': !childProjectsOpen }"
/>
<icon icon="chevron-down" :class="{ 'project-is-collapsed': !childProjectsOpen }"/>
</BaseButton>
<BaseButton
:to="{ name: 'project.index', params: { projectId: project.id} }"
@ -22,27 +19,21 @@
<span
v-if="!canCollapse || childProjects?.length === 0"
class="collapse-project-button-placeholder"
/>
<div
class="color-bubble-handle-wrapper"
:class="{'is-draggable': project.id > 0}"
>
></span>
<div class="color-bubble-handle-wrapper" :class="{'is-draggable': project.id > 0}">
<ColorBubble
v-if="project.hexColor !== ''"
:color="project.hexColor"
/>
<span
v-else-if="project.id < -1"
class="saved-filter-icon icon menu-item-icon"
>
<icon icon="filter" />
<span v-else-if="project.id < -1" class="saved-filter-icon icon menu-item-icon">
<icon icon="filter"/>
</span>
<span
v-if="project.id > 0"
class="icon menu-item-icon handle"
class="icon menu-item-icon handle lines-handle"
:class="{'has-color-bubble': project.hexColor !== ''}"
>
<icon icon="grip-lines" />
<icon icon="grip-lines"/>
</span>
</div>
<span class="project-menu-title">{{ getProjectTitle(project) }}</span>
@ -53,7 +44,7 @@
:class="{'is-favorite': project.isFavorite}"
@click="projectStore.toggleProjectFavorite(project)"
>
<icon :icon="project.isFavorite ? 'star' : ['far', 'star']" />
<icon :icon="project.isFavorite ? 'star' : ['far', 'star']"/>
</BaseButton>
<ProjectSettingsDropdown
class="menu-list-dropdown"
@ -61,14 +52,8 @@
:level="level"
>
<template #trigger="{toggleOpen}">
<BaseButton
class="menu-list-dropdown-trigger"
@click="toggleOpen"
>
<icon
icon="ellipsis-h"
class="icon"
/>
<BaseButton class="menu-list-dropdown-trigger" @click="toggleOpen">
<icon icon="ellipsis-h" class="icon"/>
</BaseButton>
</template>
</ProjectSettingsDropdown>

View File

@ -1,110 +1,66 @@
<template>
<header
:class="{ 'has-background': background, 'menu-active': menuActive }"
aria-label="main navigation"
class="navbar d-print-none"
>
<router-link
:to="{ name: 'home' }"
class="logo-link"
>
<Logo
width="164"
height="48"
/>
<header :class="{ 'has-background': background, 'menu-active': menuActive }" aria-label="main navigation"
class="navbar d-print-none">
<router-link :to="{ name: 'home' }" class="logo-link">
<Logo width="164" height="48" />
</router-link>
<MenuButton class="menu-button" />
<div
v-if="currentProject?.id"
class="project-title-wrapper"
>
<div v-if="currentProject?.id" class="project-title-wrapper">
<h1 class="project-title">
{{ currentProject.title === '' ? $t('misc.loading') : getProjectTitle(currentProject) }}
</h1>
<BaseButton
:to="{ name: 'project.info', params: { projectId: currentProject.id } }"
class="project-title-button"
>
<BaseButton :to="{ name: 'project.info', params: { projectId: currentProject.id } }" class="project-title-button">
<icon icon="circle-info" />
</BaseButton>
<ProjectSettingsDropdown
v-if="canWriteCurrentProject && currentProject.id !== -1"
class="project-title-dropdown"
:project="currentProject"
>
<project-settings-dropdown v-if="canWriteCurrentProject && currentProject.id !== -1"
class="project-title-dropdown" :project="currentProject">
<template #trigger="{ toggleOpen }">
<BaseButton
class="project-title-button"
@click="toggleOpen"
>
<icon
icon="ellipsis-h"
class="icon"
/>
<BaseButton class="project-title-button" @click="toggleOpen">
<icon icon="ellipsis-h" class="icon" />
</BaseButton>
</template>
</ProjectSettingsDropdown>
</project-settings-dropdown>
</div>
<div class="navbar-end">
<OpenQuickActions />
<OpenQuickActions/>
<Notifications />
<Dropdown>
<dropdown>
<template #trigger="{ toggleOpen, open }">
<BaseButton
class="username-dropdown-trigger"
variant="secondary"
:shadow="false"
@click="toggleOpen"
>
<img
:src="authStore.avatarUrl"
alt=""
class="avatar"
width="40"
height="40"
>
<BaseButton class="username-dropdown-trigger" @click="toggleOpen" variant="secondary" :shadow="false">
<img :src="authStore.avatarUrl" alt="" class="avatar" width="40" height="40" />
<span class="username">{{ authStore.userDisplayName }}</span>
<span
class="icon is-small"
:style="{
transform: open ? 'rotate(180deg)' : 'rotate(0)',
}"
>
<span class="icon is-small" :style="{
transform: open ? 'rotate(180deg)' : 'rotate(0)',
}">
<icon icon="chevron-down" />
</span>
</BaseButton>
</template>
<DropdownItem :to="{ name: 'user.settings' }">
<dropdown-item :to="{ name: 'user.settings' }">
{{ $t('user.settings.title') }}
</DropdownItem>
<DropdownItem
v-if="imprintUrl"
:href="imprintUrl"
>
</dropdown-item>
<dropdown-item v-if="imprintUrl" :href="imprintUrl">
{{ $t('navigation.imprint') }}
</DropdownItem>
<DropdownItem
v-if="privacyPolicyUrl"
:href="privacyPolicyUrl"
>
</dropdown-item>
<dropdown-item v-if="privacyPolicyUrl" :href="privacyPolicyUrl">
{{ $t('navigation.privacy') }}
</DropdownItem>
<DropdownItem @click="baseStore.setKeyboardShortcutsActive(true)">
</dropdown-item>
<dropdown-item @click="baseStore.setKeyboardShortcutsActive(true)">
{{ $t('keyboardShortcuts.title') }}
</DropdownItem>
<DropdownItem :to="{ name: 'about' }">
</dropdown-item>
<dropdown-item :to="{ name: 'about' }">
{{ $t('about.title') }}
</DropdownItem>
<DropdownItem @click="authStore.logout()">
</dropdown-item>
<dropdown-item @click="authStore.logout()">
{{ $t('user.auth.logout') }}
</DropdownItem>
</Dropdown>
</dropdown-item>
</dropdown>
</div>
</header>
</template>

View File

@ -1,16 +1,11 @@
<template>
<div
v-if="updateAvailable"
class="update-notification"
>
<p class="update-notification__message">
{{ $t('update.available') }}
</p>
<div class="update-notification" v-if="updateAvailable">
<p class="update-notification__message">{{ $t('update.available') }}</p>
<x-button
@click="refreshApp()"
:shadow="false"
:wrap="false"
@click="refreshApp()"
>
>
{{ $t('update.do') }}
</x-button>
</div>

View File

@ -2,10 +2,10 @@
<div class="content-auth">
<BaseButton
v-show="menuActive"
class="menu-hide-button d-print-none"
@click="baseStore.setMenuActive(false)"
class="menu-hide-button d-print-none"
>
<icon icon="times" />
<icon icon="times"/>
</BaseButton>
<div
class="app-container"
@ -15,9 +15,8 @@
<div
:class="{'is-visible': background}"
class="app-container-background background-fade-in d-print-none"
:style="{'background-image': background && `url(${background})`}"
/>
<Navigation class="d-print-none" />
:style="{'background-image': background && `url(${background})`}"></div>
<navigation class="d-print-none"/>
<main
class="app-content"
:class="[
@ -27,36 +26,33 @@
>
<BaseButton
v-show="menuActive"
class="mobile-overlay d-print-none"
@click="baseStore.setMenuActive(false)"
class="mobile-overlay d-print-none"
/>
<QuickActions />
<quick-actions/>
<router-view
v-slot="{ Component }"
:route="routeWithModal"
>
<router-view :route="routeWithModal" v-slot="{ Component }">
<keep-alive :include="['project.list', 'project.gantt', 'project.table', 'project.kanban']">
<component :is="Component" />
<component :is="Component"/>
</keep-alive>
</router-view>
<modal
:enabled="typeof currentModal !== 'undefined'"
@close="closeModal()"
variant="scrolling"
class="task-detail-view-modal"
@close="closeModal()"
>
<component :is="currentModal" />
<component :is="currentModal"/>
</modal>
<BaseButton
v-shortcut="'?'"
class="keyboard-shortcuts-button d-print-none"
@click="showKeyboardShortcuts()"
v-shortcut="'?'"
>
<icon icon="keyboard" />
<icon icon="keyboard"/>
</BaseButton>
</main>
</div>

View File

@ -6,20 +6,16 @@
>
<div class="container has-text-centered link-share-view">
<div class="column is-10 is-offset-1">
<Logo
v-if="logoVisible"
class="logo"
/>
<Logo class="logo" v-if="logoVisible"/>
<h1
:class="{'m-0': !logoVisible}"
:style="{ 'opacity': currentProject?.title === '' ? '0': '1' }"
class="title"
>
class="title">
{{ currentProject?.title === '' ? $t('misc.loading') : currentProject?.title }}
</h1>
<div class="box has-text-left view">
<router-view />
<PoweredByLink />
<router-view/>
<PoweredByLink/>
</div>
</div>
</div>

View File

@ -1,70 +1,46 @@
<template>
<aside
:class="{'is-active': baseStore.menuActive}"
class="menu-container"
>
<aside :class="{'is-active': baseStore.menuActive}" class="menu-container">
<nav class="menu top-menu">
<router-link
:to="{name: 'home'}"
class="logo"
>
<Logo
width="164"
height="48"
/>
<router-link :to="{name: 'home'}" class="logo">
<Logo width="164" height="48"/>
</router-link>
<menu class="menu-list other-menu-items">
<li>
<router-link
v-shortcut="'g o'"
:to="{ name: 'home'}"
>
<router-link :to="{ name: 'home'}" v-shortcut="'g o'">
<span class="menu-item-icon icon">
<icon icon="calendar" />
<icon icon="calendar"/>
</span>
{{ $t('navigation.overview') }}
</router-link>
</li>
<li>
<router-link
v-shortcut="'g u'"
:to="{ name: 'tasks.range'}"
>
<router-link :to="{ name: 'tasks.range'}" v-shortcut="'g u'">
<span class="menu-item-icon icon">
<icon :icon="['far', 'calendar-alt']" />
<icon :icon="['far', 'calendar-alt']"/>
</span>
{{ $t('navigation.upcoming') }}
</router-link>
</li>
<li>
<router-link
v-shortcut="'g p'"
:to="{ name: 'projects.index'}"
>
<router-link :to="{ name: 'projects.index'}" v-shortcut="'g p'">
<span class="menu-item-icon icon">
<icon icon="layer-group" />
<icon icon="layer-group"/>
</span>
{{ $t('project.projects') }}
</router-link>
</li>
<li>
<router-link
v-shortcut="'g a'"
:to="{ name: 'labels.index'}"
>
<router-link :to="{ name: 'labels.index'}" v-shortcut="'g a'">
<span class="menu-item-icon icon">
<icon icon="tags" />
<icon icon="tags"/>
</span>
{{ $t('label.title') }}
</router-link>
</li>
<li>
<router-link
v-shortcut="'g m'"
:to="{ name: 'teams.index'}"
>
<router-link :to="{ name: 'teams.index'}" v-shortcut="'g m'">
<span class="menu-item-icon icon">
<icon icon="users" />
<icon icon="users"/>
</span>
{{ $t('team.title') }}
</router-link>
@ -77,10 +53,7 @@
variant="small"
/>
<template v-else>
<nav
v-if="favoriteProjects"
class="menu"
>
<nav class="menu" v-if="favoriteProjects">
<ProjectsNavigation
:model-value="favoriteProjects"
:can-edit-order="false"
@ -88,10 +61,7 @@
/>
</nav>
<nav
v-if="savedFilterProjects"
class="menu"
>
<nav class="menu" v-if="savedFilterProjects">
<ProjectsNavigation
:model-value="savedFilterProjects"
:can-edit-order="false"
@ -109,7 +79,7 @@
</nav>
</template>
<PoweredByLink />
<PoweredByLink/>
</aside>
</template>

View File

@ -6,28 +6,19 @@ import XButton from './button.vue'
<template>
<Story :layout="{ type: 'grid', width: '200px' }">
<Variant title="primary">
<XButton
variant="primary"
@click="logEvent('Click', $event)"
>
<XButton @click="logEvent('Click', $event)" variant="primary">
Order pizza!
</XButton>
</Variant>
<Variant title="secondary">
<XButton
variant="secondary"
@click="logEvent('Click', $event)"
>
<XButton @click="logEvent('Click', $event)" variant="secondary">
Order spaghetti!
</XButton>
</Variant>
<Variant title="tertiary">
<XButton
variant="tertiary"
@click="logEvent('Click', $event)"
>
<XButton @click="logEvent('Click', $event)" variant="tertiary">
Order tortellini!
</XButton>
</Variant>

View File

@ -1,67 +1,36 @@
<template>
<div class="color-picker-container">
<datalist :id="colorListID">
<option
v-for="defaultColor in defaultColors"
:key="defaultColor"
:value="defaultColor"
/>
<option v-for="defaultColor in defaultColors" :key="defaultColor" :value="defaultColor" />
</datalist>
<div class="picker">
<input
v-model="color"
class="picker__input"
type="color"
v-model="color"
:list="colorListID"
:class="{'is-empty': isEmpty}"
>
<svg
v-show="isEmpty"
class="picker__pattern"
viewBox="0 0 22 22"
fill="fff"
>
<pattern
id="checker"
width="11"
height="11"
patternUnits="userSpaceOnUse"
fill="FFF"
>
<rect
fill="#cccccc"
x="0"
width="5.5"
height="5.5"
y="0"
/>
<rect
fill="#cccccc"
x="5.5"
width="5.5"
height="5.5"
y="5.5"
/>
/>
<svg class="picker__pattern" v-show="isEmpty" viewBox="0 0 22 22" fill="fff">
<pattern id="checker" width="11" height="11" patternUnits="userSpaceOnUse" fill="FFF">
<rect fill="#cccccc" x="0" width="5.5" height="5.5" y="0"></rect>
<rect fill="#cccccc" x="5.5" width="5.5" height="5.5" y="5.5"></rect>
</pattern>
<rect
width="22"
height="22"
fill="url(#checker)"
/>
<rect width="22" height="22" fill="url(#checker)"></rect>
</svg>
</div>
<XButton
<x-button
v-if="!isEmpty"
:disabled="isEmpty"
@click="reset"
class="is-small ml-2"
:shadow="false"
variant="secondary"
@click="reset"
>
{{ $t('input.resetColor') }}
</XButton>
</x-button>
</div>
</template>
@ -70,14 +39,6 @@ import {computed, ref, watch} from 'vue'
import {createRandomID} from '@/helpers/randomId'
import XButton from '@/components/input/button.vue'
const {
modelValue,
} = defineProps<{
modelValue: string,
}>()
const emit = defineEmits(['update:modelValue'])
const DEFAULT_COLORS = [
'#1973ff',
'#7F23FF',
@ -92,6 +53,14 @@ const lastChangeTimeout = ref<ReturnType<typeof setTimeout> | null>(null)
const defaultColors = ref(DEFAULT_COLORS)
const colorListID = ref(createRandomID())
const {
modelValue,
} = defineProps<{
modelValue: string,
}>()
const emit = defineEmits(['update:modelValue'])
watch(
() => modelValue,
(newValue) => {

View File

@ -1,5 +1,5 @@
<template>
<Multiselect
<multiselect
v-model="selectedProjects"
:search-results="foundProjects"
:loading="projectService.loading"

View File

@ -1,5 +1,5 @@
<template>
<Multiselect
<multiselect
v-model="selectedUsers"
:search-results="foundUsers"
:loading="userService.loading"

View File

@ -1,6 +1,6 @@
<template>
<BaseButton class="simple-button">
<slot />
<slot/>
</BaseButton>
</template>

View File

@ -18,10 +18,7 @@
:icon="icon"
:style="{'color': iconColor !== '' ? iconColor : undefined}"
/>
<span
v-else
class="icon is-small"
>
<span class="icon is-small" v-else>
<icon
:icon="icon"
:style="{'color': iconColor !== '' ? iconColor : undefined}"
@ -41,7 +38,7 @@ const BUTTON_TYPES_MAP = {
export type ButtonTypes = keyof typeof BUTTON_TYPES_MAP
export default { name: 'XButton' }
export default { name: 'x-button' }
</script>
<script setup lang="ts">

View File

@ -1,29 +1,22 @@
<template>
<div class="datepicker">
<SimpleButton
class="show"
:disabled="disabled || undefined"
@click.stop="toggleDatePopup"
>
<SimpleButton @click.stop="toggleDatePopup" class="show" :disabled="disabled || undefined">
{{ date === null ? chooseDateLabel : formatDateShort(date) }}
</SimpleButton>
<CustomTransition name="fade">
<div
v-if="show"
ref="datepickerPopup"
class="datepicker-popup"
>
<div v-if="show" class="datepicker-popup" ref="datepickerPopup">
<DatepickerInline
v-model="date"
@update:modelValue="updateData"
@update:model-value="updateData"
/>
<x-button
v-cy="'closeDatepicker'"
class="datepicker__close-button"
:shadow="false"
@click="close"
v-cy="'closeDatepicker'"
>
{{ $t('misc.confirm') }}
</x-button>
@ -63,7 +56,7 @@ const props = defineProps({
},
})
const emit = defineEmits(['update:modelValue', 'close', 'closeOnChange'])
const emit = defineEmits(['update:modelValue', 'close', 'close-on-change'])
const date = ref<Date | null>()
const show = ref(false)
@ -115,7 +108,7 @@ function close() {
emit('close', changed.value)
if (changed.value) {
changed.value = false
emit('closeOnChange', changed.value)
emit('close-on-change', changed.value)
}
}, 200)
}

View File

@ -4,7 +4,7 @@
class="datepicker__quick-select-date"
@click.stop="setDate('today')"
>
<span class="icon"><icon :icon="['far', 'calendar-alt']" /></span>
<span class="icon"><icon :icon="['far', 'calendar-alt']"/></span>
<span class="text">
<span>{{ $t('input.datepicker.today') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('today') }}</span>
@ -14,7 +14,7 @@
class="datepicker__quick-select-date"
@click.stop="setDate('tomorrow')"
>
<span class="icon"><icon :icon="['far', 'sun']" /></span>
<span class="icon"><icon :icon="['far', 'sun']"/></span>
<span class="text">
<span>{{ $t('input.datepicker.tomorrow') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('tomorrow') }}</span>
@ -24,7 +24,7 @@
class="datepicker__quick-select-date"
@click.stop="setDate('nextMonday')"
>
<span class="icon"><icon icon="coffee" /></span>
<span class="icon"><icon icon="coffee"/></span>
<span class="text">
<span>{{ $t('input.datepicker.nextMonday') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('nextMonday') }}</span>
@ -34,7 +34,7 @@
class="datepicker__quick-select-date"
@click.stop="setDate('thisWeekend')"
>
<span class="icon"><icon icon="cocktail" /></span>
<span class="icon"><icon icon="cocktail"/></span>
<span class="text">
<span>{{ $t('input.datepicker.thisWeekend') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('thisWeekend') }}</span>
@ -44,7 +44,7 @@
class="datepicker__quick-select-date"
@click.stop="setDate('laterThisWeek')"
>
<span class="icon"><icon icon="chess-knight" /></span>
<span class="icon"><icon icon="chess-knight"/></span>
<span class="text">
<span>{{ $t('input.datepicker.laterThisWeek') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('laterThisWeek') }}</span>
@ -54,7 +54,7 @@
class="datepicker__quick-select-date"
@click.stop="setDate('nextWeek')"
>
<span class="icon"><icon icon="forward" /></span>
<span class="icon"><icon icon="forward"/></span>
<span class="text">
<span>{{ $t('input.datepicker.nextWeek') }}</span>
<span class="weekday">{{ getWeekdayFromStringInterval('nextWeek') }}</span>
@ -63,8 +63,8 @@
<div class="flatpickr-container">
<flat-pickr
v-model="flatPickrDate"
:config="flatPickerConfig"
v-model="flatPickrDate"
/>
</div>
</template>

View File

@ -2,30 +2,26 @@
<div class="items">
<template v-if="items.length">
<button
v-for="(item, index) in items"
:key="index"
class="item"
:class="{ 'is-selected': index === selectedIndex }"
v-for="(item, index) in items"
:key="index"
@click="selectItem(index)"
>
<icon :icon="item.icon" />
<icon :icon="item.icon"/>
<div class="description">
<p>{{ item.title }}</p>
<p>{{ item.description }}</p>
</div>
</button>
</template>
<div
v-else
class="item"
>
<div class="item" v-else>
No result
</div>
</div>
</template>
<script lang="ts">
/* eslint-disable vue/component-api-style */
<script>
export default {
props: {
items: {

View File

@ -2,35 +2,35 @@
<div class="editor-toolbar">
<div class="editor-toolbar__segment">
<BaseButton
v-tooltip="$t('input.editor.heading1')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
v-tooltip="$t('input.editor.heading1')"
>
<span class="icon">
<icon :icon="['fa', 'fa-header']" />
<icon :icon="['fa', 'fa-header']"/>
<span class="icon__lower-text">1</span>
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.heading2')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
v-tooltip="$t('input.editor.heading2')"
>
<span class="icon">
<icon :icon="['fa', 'fa-header']" />
<icon :icon="['fa', 'fa-header']"/>
<span class="icon__lower-text">2</span>
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.heading3')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
v-tooltip="$t('input.editor.heading3')"
>
<span class="icon">
<icon :icon="['fa', 'fa-header']" />
<icon :icon="['fa', 'fa-header']"/>
<span class="icon__lower-text">3</span>
</span>
</BaseButton>
@ -38,167 +38,167 @@
<div class="editor-toolbar__segment">
<BaseButton
v-tooltip="$t('input.editor.bold')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('bold') }"
@click="editor.chain().focus().toggleBold().run()"
:class="{ 'is-active': editor.isActive('bold') }"
v-tooltip="$t('input.editor.bold')"
>
<span class="icon">
<icon :icon="['fa', 'fa-bold']" />
<icon :icon="['fa', 'fa-bold']"/>
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.italic')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('italic') }"
@click="editor.chain().focus().toggleItalic().run()"
:class="{ 'is-active': editor.isActive('italic') }"
v-tooltip="$t('input.editor.italic')"
>
<span class="icon">
<icon :icon="['fa', 'fa-italic']" />
<icon :icon="['fa', 'fa-italic']"/>
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.underline')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('underline') }"
@click="editor.chain().focus().toggleUnderline().run()"
:class="{ 'is-active': editor.isActive('underline') }"
v-tooltip="$t('input.editor.underline')"
>
<span class="icon">
<icon :icon="['fa', 'fa-underline']" />
<icon :icon="['fa', 'fa-underline']"/>
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.strikethrough')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('strike') }"
@click="editor.chain().focus().toggleStrike().run()"
:class="{ 'is-active': editor.isActive('strike') }"
v-tooltip="$t('input.editor.strikethrough')"
>
<span class="icon">
<icon :icon="['fa', 'fa-strikethrough']" />
<icon :icon="['fa', 'fa-strikethrough']"/>
</span>
</BaseButton>
</div>
<div class="editor-toolbar__segment">
<BaseButton
v-tooltip="$t('input.editor.code')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('codeBlock') }"
@click="editor.chain().focus().toggleCodeBlock().run()"
:class="{ 'is-active': editor.isActive('codeBlock') }"
v-tooltip="$t('input.editor.code')"
>
<span class="icon">
<icon :icon="['fa', 'fa-code']" />
<icon :icon="['fa', 'fa-code']"/>
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.quote')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('blockquote') }"
@click="editor.chain().focus().toggleBlockquote().run()"
:class="{ 'is-active': editor.isActive('blockquote') }"
v-tooltip="$t('input.editor.quote')"
>
<span class="icon">
<icon :icon="['fa', 'fa-quote-right']" />
<icon :icon="['fa', 'fa-quote-right']"/>
</span>
</BaseButton>
</div>
<div class="editor-toolbar__segment">
<BaseButton
v-tooltip="$t('input.editor.bulletList')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('bulletList') }"
@click="editor.chain().focus().toggleBulletList().run()"
:class="{ 'is-active': editor.isActive('bulletList') }"
v-tooltip="$t('input.editor.bulletList')"
>
<span class="icon">
<icon :icon="['fa', 'fa-list-ul']" />
<icon :icon="['fa', 'fa-list-ul']"/>
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.orderedList')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('orderedList') }"
@click="editor.chain().focus().toggleOrderedList().run()"
:class="{ 'is-active': editor.isActive('orderedList') }"
v-tooltip="$t('input.editor.orderedList')"
>
<span class="icon">
<icon :icon="['fa', 'fa-list-ol']" />
<icon :icon="['fa', 'fa-list-ol']"/>
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.taskList')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('taskList') }"
@click="editor.chain().focus().toggleTaskList().run()"
:class="{ 'is-active': editor.isActive('taskList') }"
v-tooltip="$t('input.editor.taskList')"
>
<span class="icon">
<icon icon="fa-list-check" />
<icon icon="fa-list-check"/>
</span>
</BaseButton>
</div>
<div class="editor-toolbar__segment">
<BaseButton
v-tooltip="$t('input.editor.image')"
class="editor-toolbar__button"
@click="openImagePicker"
@click="openImagePicker"
v-tooltip="$t('input.editor.image')"
>
<span class="icon">
<icon icon="fa-image" />
<icon icon="fa-image"/>
</span>
</BaseButton>
</div>
<div class="editor-toolbar__segment">
<BaseButton
v-tooltip="$t('input.editor.link')"
class="editor-toolbar__button"
@click="setLink"
:class="{ 'is-active': editor.isActive('link') }"
title="set link"
@click="setLink"
v-tooltip="$t('input.editor.link')"
>
<span class="icon">
<icon :icon="['fa', 'fa-link']" />
<icon :icon="['fa', 'fa-link']"/>
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.text')"
class="editor-toolbar__button"
@click="editor.chain().focus().setParagraph().run()"
:class="{ 'is-active': editor.isActive('paragraph') }"
title="paragraph"
@click="editor.chain().focus().setParagraph().run()"
v-tooltip="$t('input.editor.text')"
>
<span class="icon">
<icon :icon="['fa', 'fa-paragraph']" />
<icon :icon="['fa', 'fa-paragraph']"/>
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.horizontalRule')"
class="editor-toolbar__button"
@click="editor.chain().focus().setHorizontalRule().run()"
v-tooltip="$t('input.editor.horizontalRule')"
>
<span class="icon">
<icon :icon="['fa', 'fa-ruler-horizontal']" />
<icon :icon="['fa', 'fa-ruler-horizontal']"/>
</span>
</BaseButton>
</div>
<div class="editor-toolbar__segment">
<BaseButton
v-tooltip="$t('input.editor.undo')"
class="editor-toolbar__button"
@click="editor.chain().focus().undo().run()"
v-tooltip="$t('input.editor.undo')"
>
<span class="icon">
<icon :icon="['fa', 'fa-undo']" />
<icon :icon="['fa', 'fa-undo']"/>
</span>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.redo')"
class="editor-toolbar__button"
@click="editor.chain().focus().redo().run()"
v-tooltip="$t('input.editor.redo')"
>
<span class="icon">
<icon :icon="['fa', 'fa-redo']" />
<icon :icon="['fa', 'fa-redo']"/>
</span>
</BaseButton>
</div>
@ -206,19 +206,16 @@
<div class="editor-toolbar__segment">
<!-- table -->
<BaseButton
v-tooltip="$t('input.editor.table.title')"
class="editor-toolbar__button"
:class="{ 'is-active': editor.isActive('table') }"
@click="toggleTableMode"
:class="{ 'is-active': editor.isActive('table') }"
v-tooltip="$t('input.editor.table.title')"
>
<span class="icon">
<icon :icon="['fa', 'fa-table']" />
<icon :icon="['fa', 'fa-table']"/>
</span>
</BaseButton>
<div
v-if="tableMode"
class="editor-toolbar__table-buttons"
>
<div v-if="tableMode" class="editor-toolbar__table-buttons">
<BaseButton
class="editor-toolbar__button"
@click="
@ -233,99 +230,99 @@
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().addColumnBefore"
@click="editor.chain().focus().addColumnBefore().run()"
:disabled="!editor.can().addColumnBefore"
>
{{ $t('input.editor.table.addColumnBefore') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().addColumnAfter"
@click="editor.chain().focus().addColumnAfter().run()"
:disabled="!editor.can().addColumnAfter"
>
{{ $t('input.editor.table.addColumnAfter') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().deleteColumn"
@click="editor.chain().focus().deleteColumn().run()"
:disabled="!editor.can().deleteColumn"
>
{{ $t('input.editor.table.deleteColumn') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().addRowBefore"
@click="editor.chain().focus().addRowBefore().run()"
:disabled="!editor.can().addRowBefore"
>
{{ $t('input.editor.table.addRowBefore') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().addRowAfter"
@click="editor.chain().focus().addRowAfter().run()"
:disabled="!editor.can().addRowAfter"
>
{{ $t('input.editor.table.addRowAfter') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().deleteRow"
@click="editor.chain().focus().deleteRow().run()"
:disabled="!editor.can().deleteRow"
>
{{ $t('input.editor.table.deleteRow') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().deleteTable"
@click="editor.chain().focus().deleteTable().run()"
:disabled="!editor.can().deleteTable"
>
{{ $t('input.editor.table.deleteTable') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().mergeCells"
@click="editor.chain().focus().mergeCells().run()"
:disabled="!editor.can().mergeCells"
>
{{ $t('input.editor.table.mergeCells') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().splitCell"
@click="editor.chain().focus().splitCell().run()"
:disabled="!editor.can().splitCell"
>
{{ $t('input.editor.table.splitCell') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().toggleHeaderColumn"
@click="editor.chain().focus().toggleHeaderColumn().run()"
:disabled="!editor.can().toggleHeaderColumn"
>
{{ $t('input.editor.table.toggleHeaderColumn') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().toggleHeaderRow"
@click="editor.chain().focus().toggleHeaderRow().run()"
:disabled="!editor.can().toggleHeaderRow"
>
{{ $t('input.editor.table.toggleHeaderRow') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().toggleHeaderCell"
@click="editor.chain().focus().toggleHeaderCell().run()"
:disabled="!editor.can().toggleHeaderCell"
>
{{ $t('input.editor.table.toggleHeaderCell') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().mergeOrSplit"
@click="editor.chain().focus().mergeOrSplit().run()"
:disabled="!editor.can().mergeOrSplit"
>
{{ $t('input.editor.table.mergeOrSplit') }}
</BaseButton>
<BaseButton
class="editor-toolbar__button"
:disabled="!editor.can().fixTables"
@click="editor.chain().focus().fixTables().run()"
:disabled="!editor.can().fixTables"
>
{{ $t('input.editor.table.fixTables') }}
</BaseButton>

View File

@ -1,8 +1,5 @@
<template>
<div
ref="tiptapInstanceRef"
class="tiptap"
>
<div class="tiptap" ref="tiptapInstanceRef">
<EditorToolbar
v-if="editor && isEditing"
:editor="editor"
@ -14,56 +11,56 @@
class="editor-bubble__wrapper"
>
<BaseButton
v-tooltip="$t('input.editor.bold')"
class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('bold') }"
@click="editor.chain().focus().toggleBold().run()"
:class="{ 'is-active': editor.isActive('bold') }"
v-tooltip="$t('input.editor.bold')"
>
<icon :icon="['fa', 'fa-bold']" />
<icon :icon="['fa', 'fa-bold']"/>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.italic')"
class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('italic') }"
@click="editor.chain().focus().toggleItalic().run()"
:class="{ 'is-active': editor.isActive('italic') }"
v-tooltip="$t('input.editor.italic')"
>
<icon :icon="['fa', 'fa-italic']" />
<icon :icon="['fa', 'fa-italic']"/>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.underline')"
class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('underline') }"
@click="editor.chain().focus().toggleUnderline().run()"
:class="{ 'is-active': editor.isActive('underline') }"
v-tooltip="$t('input.editor.underline')"
>
<icon :icon="['fa', 'fa-underline']" />
<icon :icon="['fa', 'fa-underline']"/>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.strikethrough')"
class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('strike') }"
@click="editor.chain().focus().toggleStrike().run()"
:class="{ 'is-active': editor.isActive('strike') }"
v-tooltip="$t('input.editor.strikethrough')"
>
<icon :icon="['fa', 'fa-strikethrough']" />
<icon :icon="['fa', 'fa-strikethrough']"/>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.code')"
class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('code') }"
@click="editor.chain().focus().toggleCode().run()"
:class="{ 'is-active': editor.isActive('code') }"
v-tooltip="$t('input.editor.code')"
>
<icon :icon="['fa', 'fa-code']" />
<icon :icon="['fa', 'fa-code']"/>
</BaseButton>
<BaseButton
v-tooltip="$t('input.editor.link')"
class="editor-bubble__button"
:class="{ 'is-active': editor.isActive('link') }"
@click="setLink"
:class="{ 'is-active': editor.isActive('link') }"
v-tooltip="$t('input.editor.link')"
>
<icon :icon="['fa', 'fa-link']" />
<icon :icon="['fa', 'fa-link']"/>
</BaseButton>
</BubbleMenu>
<EditorContent
<editor-content
class="tiptap__editor"
:class="{'tiptap__editor-is-edit-enabled': isEditing}"
:editor="editor"
@ -72,66 +69,52 @@
<input
v-if="isEditing"
id="tiptap__image-upload"
ref="uploadInputRef"
type="file"
id="tiptap__image-upload"
class="is-hidden"
ref="uploadInputRef"
@change="addImage"
>
/>
<ul
v-if="bottomActions.length === 0 && !isEditing && isEditEnabled"
class="tiptap__editor-actions d-print-none"
>
<ul class="tiptap__editor-actions d-print-none" v-if="bottomActions.length === 0 && !isEditing && isEditEnabled">
<li>
<BaseButton
class="done-edit"
@click="setEdit"
>
class="done-edit">
{{ $t('input.editor.edit') }}
</BaseButton>
</li>
</ul>
<ul
v-if="bottomActions.length > 0"
class="tiptap__editor-actions d-print-none"
>
<ul class="tiptap__editor-actions d-print-none" v-if="bottomActions.length > 0">
<li v-if="isEditing && showSave">
<BaseButton
class="done-edit"
@click="bubbleSave"
>
class="done-edit">
{{ $t('misc.save') }}
</BaseButton>
</li>
<li v-if="!isEditing">
<BaseButton
class="done-edit"
@click="setEdit"
>
class="done-edit">
{{ $t('input.editor.edit') }}
</BaseButton>
</li>
<li
v-for="(action, k) in bottomActions"
:key="k"
>
<BaseButton @click="action.action">
{{ action.title }}
</BaseButton>
<li v-for="(action, k) in bottomActions" :key="k">
<BaseButton @click="action.action">{{ action.title }}</BaseButton>
</li>
</ul>
<XButton
<x-button
v-else-if="isEditing && showSave"
v-cy="'saveEditor'"
class="mt-4"
@click="bubbleSave"
variant="secondary"
:shadow="false"
v-cy="'saveEditor'"
:disabled="!contentHasChanged"
@click="bubbleSave"
>
{{ $t('misc.save') }}
</XButton>
</x-button>
</div>
</template>
@ -194,26 +177,6 @@ import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
import inputPrompt from '@/helpers/inputPrompt'
import {setLinkInEditor} from '@/components/input/editor/setLinkInEditor'
const {
modelValue,
uploadCallback,
isEditEnabled = true,
bottomActions = [],
showSave = false,
placeholder = '',
editShortcut = '',
} = defineProps<{
modelValue: string,
uploadCallback?: UploadCallback,
isEditEnabled?: boolean,
bottomActions?: BottomAction[],
showSave?: boolean,
placeholder?: string,
editShortcut?: string,
}>()
const emit = defineEmits(['update:modelValue', 'save'])
const tiptapInstanceRef = ref<HTMLInputElement | null>(null)
const {t} = useI18n()
@ -307,6 +270,26 @@ const CustomImage = Image.extend({
type Mode = 'edit' | 'preview'
const {
modelValue,
uploadCallback,
isEditEnabled = true,
bottomActions = [],
showSave = false,
placeholder = '',
editShortcut = '',
} = defineProps<{
modelValue: string,
uploadCallback?: UploadCallback,
isEditEnabled?: boolean,
bottomActions?: BottomAction[],
showSave?: boolean,
placeholder?: string,
editShortcut?: string,
}>()
const emit = defineEmits(['update:modelValue', 'save'])
const internalMode = ref<Mode>('preview')
const isEditing = computed(() => internalMode.value === 'edit' && isEditEnabled)
const contentHasChanged = ref<boolean>(false)
@ -321,7 +304,7 @@ watch(
)
const editor = useEditor({
// eslint-disable-next-line vue/no-ref-object-destructure
content: modelValue,
editable: isEditing.value,
extensions: [
// Starterkit:
@ -763,7 +746,7 @@ watch(
height: auto;
&.ProseMirror-selectednode {
outline: 3px solid var(--primary);
outline: 3px solid #68cef8;
}
}

View File

@ -26,42 +26,32 @@ const withoutInitialState = ref<boolean | undefined>()
</FancyCheckbox>
Visualisation
<input
v-model="isChecked"
type="checkbox"
>
<input type="checkbox" v-model="isChecked">
{{ isChecked }}
</Variant>
<Variant title="Enabled Initially">
<FancyCheckbox
v-model="isCheckedInitiallyEnabled"
:disabled="isDisabled"
v-model="isCheckedInitiallyEnabled"
>
We want you to use this option
</FancyCheckbox>
Visualisation
<input
v-model="isCheckedInitiallyEnabled"
type="checkbox"
>
<input type="checkbox" v-model="isCheckedInitiallyEnabled">
{{ isCheckedInitiallyEnabled }}
</Variant>
<Variant title="Disabled">
<FancyCheckbox
disabled
:model-value="isCheckedDisabled"
@update:modelValue="logEvent('Setting disabled: This should never happen', $event)"
:modelValue="isCheckedDisabled"
@update:model-value="logEvent('Setting disabled: This should never happen', $event)"
>
You can't change this
</FancyCheckbox>
Visualisation
<input
v-model="isCheckedDisabled"
type="checkbox"
disabled
>
<input type="checkbox" v-model="isCheckedDisabled" disabled>
{{ isCheckedDisabled }}
</Variant>
@ -74,11 +64,7 @@ const withoutInitialState = ref<boolean | undefined>()
</FancyCheckbox>
Visualisation
<input
v-model="withoutInitialState"
type="checkbox"
disabled
>
<input type="checkbox" v-model="withoutInitialState" disabled>
{{ withoutInitialState }}
</Variant>
</Story>

View File

@ -7,14 +7,11 @@
}"
:disabled="disabled"
:model-value="modelValue"
@update:modelValue="value => emit('update:modelValue', value)"
@update:model-value="value => emit('update:modelValue', value)"
>
<CheckboxIcon class="fancycheckbox__icon" />
<span
v-if="$slots.default"
class="fancycheckbox__content"
>
<slot />
<span v-if="$slots.default" class="fancycheckbox__content">
<slot/>
</span>
</BaseCheckbox>
</template>

View File

@ -1,15 +1,12 @@
<template>
<div
ref="multiselectRoot"
class="multiselect"
:class="{'has-search-results': searchResultsVisible}"
ref="multiselectRoot"
tabindex="-1"
@focus="focus"
>
<div
class="control"
:class="{'is-loading': loading || localLoading}"
>
<div class="control" :class="{'is-loading': loading || localLoading}">
<div
class="input-wrapper input"
:class="{'has-multiple': hasMultiple, 'has-removal-button': removalAvailable}"
@ -21,67 +18,51 @@
:remove="remove"
>
<template v-for="(item, key) in internalValue">
<slot
name="tag"
:item="item"
>
<span
:key="`item${key}`"
class="tag ml-2 mt-2"
>
<slot name="tag" :item="item">
<span :key="`item${key}`" class="tag ml-2 mt-2">
{{ label !== '' ? item[label] : item }}
<BaseButton
class="delete is-small"
@click="() => remove(item)"
/>
<BaseButton @click="() => remove(item)" class="delete is-small"></BaseButton>
</span>
</slot>
</template>
</slot>
<input
ref="searchInput"
v-model="query"
type="text"
class="input"
:placeholder="placeholder"
:autocomplete="autocompleteEnabled ? undefined : 'off'"
:spellcheck="autocompleteEnabled ? undefined : 'false'"
v-model="query"
@keyup="search"
@keyup.enter.exact.prevent="() => createOrSelectOnEnter()"
:placeholder="placeholder"
@keydown.down.exact.prevent="() => preSelect(0)"
ref="searchInput"
@focus="handleFocus"
>
:autocomplete="autocompleteEnabled ? undefined : 'off'"
:spellcheck="autocompleteEnabled ? undefined : 'false'"
/>
<BaseButton
v-if="removalAvailable"
class="removal-button"
@click="resetSelectedValue"
>
<icon icon="times" />
<icon icon="times"/>
</BaseButton>
</div>
</div>
<CustomTransition name="fade">
<div
v-if="searchResultsVisible"
class="search-results"
:class="{'search-results-inline': inline}"
>
<div class="search-results" :class="{'search-results-inline': inline}" v-if="searchResultsVisible">
<BaseButton
class="search-result-button is-fullwidth"
v-for="(data, index) in filteredSearchResults"
:key="index"
:ref="(el) => setResult(el, index)"
class="search-result-button is-fullwidth"
@keydown.up.prevent="() => preSelect(index - 1)"
@keydown.down.prevent="() => preSelect(index + 1)"
@click.prevent.stop="() => select(data)"
>
<span>
<slot
name="searchResult"
:option="data"
>
<slot name="searchResult" :option="data">
<span class="search-result">{{ label !== '' ? data[label] : data }}</span>
</slot>
</span>
@ -92,18 +73,15 @@
<BaseButton
v-if="creatableAvailable"
:ref="(el) => setResult(el, filteredSearchResults.length)"
class="search-result-button is-fullwidth"
:ref="(el) => setResult(el, filteredSearchResults.length)"
@keydown.up.prevent="() => preSelect(filteredSearchResults.length - 1)"
@keydown.down.prevent="() => preSelect(filteredSearchResults.length + 1)"
@keyup.enter.prevent="create"
@click.prevent.stop="create"
>
<span>
<slot
name="searchResult"
:option="query"
>
<slot name="searchResult" :option="query">
<span class="search-result">
{{ query }}
</span>
@ -129,6 +107,16 @@ import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import BaseButton from '@/components/base/BaseButton.vue'
import CustomTransition from '@/components/misc/CustomTransition.vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function elementInResults(elem: string | any, label: string, query: string): boolean {
// Don't make create available if we have an exact match in our search results.
if (label !== '') {
return elem[label] === query
}
return elem === query
}
const props = defineProps({
/**
* When true, shows a loading spinner
@ -257,16 +245,6 @@ const emit = defineEmits<{
(e: 'remove', value: null): void
}>()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function elementInResults(elem: string | any, label: string, query: string): boolean {
// Don't make create available if we have an exact match in our search results.
if (label !== '') {
return elem[label] === query
}
return elem === query
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const query = ref<string | { [key: string]: any }>('')
const searchTimeout = ref<ReturnType<typeof setTimeout> | null>(null)

View File

@ -1,31 +1,27 @@
<template>
<div class="password-field">
<input
id="password"
class="input"
id="password"
name="password"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
:type="passwordFieldType"
autocomplete="current-password"
:tabindex="props.tabindex"
@keyup.enter="e => $emit('submit', e)"
:tabindex="props.tabindex"
@focusout="validate"
@input="handleInput"
>
/>
<BaseButton
v-tooltip="passwordFieldType === 'password' ? $t('user.auth.showPassword') : $t('user.auth.hidePassword')"
@click="togglePasswordFieldType"
class="password-field-type-toggle"
:aria-label="passwordFieldType === 'password' ? $t('user.auth.showPassword') : $t('user.auth.hidePassword')"
@click="togglePasswordFieldType"
>
<icon :icon="passwordFieldType === 'password' ? 'eye' : 'eye-slash'" />
v-tooltip="passwordFieldType === 'password' ? $t('user.auth.showPassword') : $t('user.auth.hidePassword')">
<icon :icon="passwordFieldType === 'password' ? 'eye' : 'eye-slash'"/>
</BaseButton>
</div>
<p
v-if="!isValid"
class="help is-danger"
>
<p class="help is-danger" v-if="!isValid">
{{ $t('user.auth.passwordRequired') }}
</p>
</template>

View File

@ -1,7 +1,5 @@
<template>
<BaseButton class="button-link">
<slot />
</BaseButton>
<BaseButton class="button-link"><slot/></BaseButton>
</template>
<script setup lang="ts">

View File

@ -1,5 +1,5 @@
<template>
<div
<div
v-if="isDone"
class="is-done"
:class="{ 'is-done--small': variant === 'small' }"

View File

@ -31,10 +31,10 @@ function openQuickActions() {
<template>
<BaseButton
@click="openQuickActions"
class="trigger-button"
:title="$t('keyboardShortcuts.quickSearch')"
@click="openQuickActions"
>
<icon icon="search" />
<icon icon="search"/>
</BaseButton>
</template>

View File

@ -1,15 +0,0 @@
<script setup lang="ts">
import {ref} from 'vue'
import ProgressBar from './ProgressBar.vue'
const value = ref(50)
</script>
<template>
<Story>
<Variant title="Default">
<ProgressBar :value="value" />
</Variant>
</Story>
</template>

View File

@ -1,139 +0,0 @@
<template>
<progress
class="progress-bar"
:class="{
'is-small': isSmall,
'is-primary': isPrimary,
}"
:value="value"
max="100"
>
{{ value }}%
</progress>
</template>
<script setup lang="ts">
import {defineProps} from 'vue'
defineProps({
value: {
type: Number,
required: true,
},
isSmall: {
type: Boolean,
default: false,
},
isPrimary: {
type: Boolean,
required: false,
},
})
</script>
<style lang="scss" scoped>
.progress-bar {
--progress-height: #{$size-normal};
--progress-bar-background-color: var(--border-light, #{$border-light});
--progress-value-background-color: var(--grey-500, #{$text});
--progress-border-radius: #{$radius};
--progress-indeterminate-duration: 1.5s;
appearance: none;
border: none;
border-radius: var(--progress-border-radius);
height: var(--progress-height);
overflow: hidden;
padding: 0;
min-width: 6vw;
width: 50px;
margin: 0 .5rem 0 0;
flex: 3 1 auto;
&::-moz-progress-bar,
&::-webkit-progress-value {
background: var(--progress-value-background-color);
}
@media screen and (max-width: $tablet) {
margin: 0.5rem 0 0 0;
order: 1;
width: 100%;
}
&::-webkit-progress-bar {
background-color: var(--progress-bar-background-color);
}
&::-webkit-progress-value {
background-color: var(--progress-value-background-color);
}
&::-moz-progress-bar {
background-color: var(--progress-value-background-color);
}
&::-ms-fill {
background-color: var(--progress-value-background-color);
border: none;
}
// Colors
@each $name, $pair in $colors {
$color: nth($pair, 1);
&.is-#{$name} {
--progress-value-background-color: var(--#{$name}, #{$color});
&:indeterminate {
background-image: linear-gradient(
to right,
var(--#{$name}, #{$color}) 30%,
var(--progress-bar-background-color) 30%
);
}
}
}
&:indeterminate {
animation-duration: var(--progress-indeterminate-duration);
animation-iteration-count: infinite;
animation-name: moveIndeterminate;
animation-timing-function: linear;
background-color: var(--progress-bar-background-color);
background-image: linear-gradient(
to right,
var(--text, #{$text}) 30%,
var(--progress-bar-background-color) 30%
);
background-position: top left;
background-repeat: no-repeat;
background-size: 150% 150%;
&::-webkit-progress-bar {
background-color: transparent;
}
&::-moz-progress-bar {
background-color: transparent;
}
&::-ms-fill {
animation-name: none;
}
}
&.is-small {
--progress-height: #{$size-small};
}
}
@keyframes moveIndeterminate {
from {
background-position: 200% 0;
}
to {
background-position: -200% 0;
}
}
</style>

View File

@ -1,62 +1,38 @@
<template>
<div class="api-config">
<div v-if="configureApi">
<label
class="label"
for="api-url"
>{{ $t('apiConfig.url') }}</label>
<label class="label" for="api-url">{{ $t('apiConfig.url') }}</label>
<div class="field has-addons">
<div class="control is-expanded">
<input
id="api-url"
v-model="apiUrl"
v-focus
class="input"
id="api-url"
:placeholder="$t('apiConfig.urlPlaceholder')"
required
type="url"
v-focus
v-model="apiUrl"
@keyup.enter="setApiUrl"
>
/>
</div>
<div class="control">
<x-button
:disabled="apiUrl === '' || undefined"
@click="setApiUrl"
>
<x-button @click="setApiUrl" :disabled="apiUrl === '' || undefined">
{{ $t('apiConfig.change') }}
</x-button>
</div>
</div>
</div>
<div
v-else
class="api-url-info"
>
<i18n-t
keypath="apiConfig.use"
scope="global"
>
<span
v-tooltip="apiUrl"
class="url"
> {{ apiDomain }} </span>
<div class="api-url-info" v-else>
<i18n-t keypath="apiConfig.use" scope="global">
<span class="url" v-tooltip="apiUrl"> {{ apiDomain }} </span>
</i18n-t>
<br>
<ButtonLink
class="api-config__change-button"
@click="() => (configureApi = true)"
>
{{ $t('apiConfig.change') }}
</ButtonLink>
<br/>
<ButtonLink class="api-config__change-button" @click="() => (configureApi = true)">{{ $t('apiConfig.change') }}</ButtonLink>
</div>
<Message
v-if="errorMsg !== ''"
variant="danger"
class="mt-2"
>
<message variant="danger" v-if="errorMsg !== ''" class="mt-2">
{{ errorMsg }}
</Message>
</message>
</div>
</template>
@ -81,7 +57,7 @@ const props = defineProps({
const emit = defineEmits(['foundApi'])
const apiUrl = ref(window.API_URL)
const configureApi = ref(window.API_URL === '')
const configureApi = ref(apiUrl.value === '')
// Because we're only using this to parse the hostname, it should be fine to just prefix with http://
// regardless of whether the url is actually reachable under http.

View File

@ -1,24 +1,18 @@
<template>
<div
class="card"
:class="{'has-no-shadow': !shadow}"
>
<header
v-if="title !== ''"
class="card-header"
>
<div class="card" :class="{'has-no-shadow': !shadow}">
<header class="card-header" v-if="title !== ''">
<p class="card-header-title">
{{ title }}
</p>
<BaseButton
v-if="hasClose"
v-tooltip="$t('misc.close')"
class="card-header-icon"
:aria-label="$t('misc.close')"
@click="$emit('close')"
v-tooltip="$t('misc.close')"
>
<span class="icon">
<icon :icon="closeIcon" />
<icon :icon="closeIcon"/>
</span>
</BaseButton>
</header>
@ -30,15 +24,12 @@
}"
>
<div :class="{'content': hasContent}">
<slot />
<slot/>
</div>
</div>
<footer
v-if="$slots.footer"
class="card-footer"
>
<slot name="footer" />
<footer v-if="$slots.footer" class="card-footer">
<slot name="footer"/>
</footer>
</div>
</template>

View File

@ -2,7 +2,7 @@
<span
:style="{backgroundColor: color }"
class="color-bubble"
/>
></span>
</template>
<script lang="ts" setup>

View File

@ -1,20 +1,16 @@
<template>
<modal
:overflow="true"
:wide="wide"
@close="$router.back()"
>
<modal @close="$router.back()" :overflow="true" :wide="wide">
<card
:title="title"
:shadow="false"
:padding="false"
class="has-text-left"
:has-close="true"
:loading="loading"
@close="$router.back()"
:loading="loading"
>
<div class="p-4">
<slot />
<slot/>
</div>
<template #footer>
@ -36,10 +32,10 @@
<x-button
v-if="hasPrimaryAction"
variant="primary"
@click.prevent.stop="primary()"
:icon="primaryIcon"
:disabled="primaryDisabled || loading"
class="ml-2"
@click.prevent.stop="primary()"
>
{{ primaryLabel || $t('misc.create') }}
</x-button>

View File

@ -5,10 +5,10 @@
class="icon is-small"
:class="iconClass"
>
<Icon :icon="icon" />
<Icon :icon="icon"/>
</span>
<span>
<slot />
<slot/>
</span>
</BaseButton>
</template>

View File

@ -1,32 +1,15 @@
<template>
<div
ref="dropdown"
class="dropdown"
>
<slot
name="trigger"
:close="close"
:toggle-open="toggleOpen"
:open="open"
>
<BaseButton
class="dropdown-trigger is-flex"
@click="toggleOpen"
>
<icon
:icon="triggerIcon"
class="icon"
/>
<div class="dropdown" ref="dropdown">
<slot name="trigger" :close="close" :toggleOpen="toggleOpen" :open="open">
<BaseButton class="dropdown-trigger is-flex" @click="toggleOpen">
<icon :icon="triggerIcon" class="icon"/>
</BaseButton>
</slot>
<CustomTransition name="fade">
<div
v-if="open"
class="dropdown-menu"
>
<div class="dropdown-menu" v-if="open">
<div class="dropdown-content">
<slot :close="close" />
<slot :close="close"></slot>
</div>
</div>
</CustomTransition>

View File

@ -1,17 +1,10 @@
<template>
<Message variant="danger">
<i18n-t
keypath="loadingError.failed"
scope="global"
>
<ButtonLink @click="reload">
{{ $t('loadingError.tryAgain') }}
</ButtonLink>
<ButtonLink href="https://vikunja.io/contact/">
{{ $t('loadingError.contact') }}
</ButtonLink>
<message variant="danger">
<i18n-t keypath="loadingError.failed" scope="global">
<ButtonLink @click="reload">{{ $t('loadingError.tryAgain') }}</ButtonLink>
<ButtonLink href="https://vikunja.io/contact/">{{ $t('loadingError.contact') }}</ButtonLink>
</i18n-t>
</Message>
</message>
</template>
<script lang="ts">

View File

@ -1,11 +1,11 @@
<template>
<input
v-bind="attrs"
ref="root"
type="text"
data-input
:disabled="disabled"
>
v-bind="attrs"
ref="root"
/>
</template>
<script lang="ts">

View File

@ -1,20 +1,10 @@
<template>
<modal @close="close()">
<card
class="has-background-white keyboard-shortcuts"
:shadow="false"
:title="$t('keyboardShortcuts.title')"
>
<template
v-for="(s, i) in shortcuts"
:key="i"
>
<card class="has-background-white keyboard-shortcuts" :shadow="false" :title="$t('keyboardShortcuts.title')">
<template v-for="(s, i) in shortcuts" :key="i">
<h3>{{ $t(s.title) }}</h3>
<Message
v-if="s.available"
class="mb-4"
>
<message class="mb-4" v-if="s.available">
{{
typeof s.available === 'undefined' ?
$t('keyboardShortcuts.allPages') :
@ -24,19 +14,14 @@
: $t('keyboardShortcuts.somePagesOnly')
)
}}
</Message>
</message>
<dl class="shortcut-list">
<template
v-for="(sc, si) in s.shortcuts"
:key="si"
>
<dt class="shortcut-title">
{{ $t(sc.title) }}
</dt>
<Shortcut
is="dd"
<template v-for="(sc, si) in s.shortcuts" :key="si">
<dt class="shortcut-title">{{ $t(sc.title) }}</dt>
<shortcut
class="shortcut-keys"
is="dd"
:keys="sc.keys"
:combination="sc.combination && $t(`keyboardShortcuts.${sc.combination}`)"
/>

View File

@ -1,18 +1,8 @@
<template>
<div class="legal-links">
<BaseButton
v-if="imprintUrl"
:href="imprintUrl"
>
{{ $t('navigation.imprint') }}
</BaseButton>
<BaseButton :href="imprintUrl" v-if="imprintUrl">{{ $t('navigation.imprint') }}</BaseButton>
<span v-if="imprintUrl && privacyPolicyUrl"> | </span>
<BaseButton
v-if="privacyPolicyUrl"
:href="privacyPolicyUrl"
>
{{ $t('navigation.privacy') }}
</BaseButton>
<BaseButton :href="privacyPolicyUrl" v-if="privacyPolicyUrl">{{ $t('navigation.privacy') }}</BaseButton>
</div>
</template>

View File

@ -1,8 +1,5 @@
<template>
<div
class="loader-container is-loading"
:class="{'is-small': variant === 'small'}"
/>
<div class="loader-container is-loading" :class="{'is-small': variant === 'small'}"></div>
</template>
<script lang="ts">

View File

@ -1,10 +1,7 @@
<template>
<div class="message-wrapper">
<div
class="message"
:class="[variant, textAlignClass]"
>
<slot />
<div class="message" :class="[variant, textAlignClass]">
<slot/>
</div>
</div>
</template>
@ -12,6 +9,14 @@
<script lang="ts" setup>
import {computed, type PropType} from 'vue'
const TEXT_ALIGN_MAP = Object.freeze({
left: '',
center: 'has-text-centered',
right: 'has-text-right',
})
type textAlignVariants = keyof typeof TEXT_ALIGN_MAP
const props = defineProps({
variant: {
type: String,
@ -23,14 +28,6 @@ const props = defineProps({
},
})
const TEXT_ALIGN_MAP = Object.freeze({
left: '',
center: 'has-text-centered',
right: 'has-text-right',
})
type textAlignVariants = keyof typeof TEXT_ALIGN_MAP
const textAlignClass = computed(() => TEXT_ALIGN_MAP[props.textAlign])
</script>

View File

@ -1,59 +1,56 @@
<template>
<Teleport to="body">
<!-- FIXME: transition should not be included in the modal -->
<CustomTransition
:name="transitionName"
appear
>
<CustomTransition :name="transitionName" appear>
<section
v-if="enabled"
ref="modal"
class="modal-mask"
:class="[
{ 'has-overflow': overflow },
variant,
]"
ref="modal"
v-bind="attrs"
>
<div
v-shortcut="'Escape'"
class="modal-container"
@mousedown.self.prevent.stop="$emit('close')"
v-shortcut="'Escape'"
>
<div
class="modal-content"
:class="{
'has-overflow': overflow,
'is-wide': wide
}"
'has-overflow': overflow,
'is-wide': wide
}"
>
<BaseButton
class="close"
@click="$emit('close')"
class="close"
>
<icon icon="times" />
<icon icon="times"/>
</BaseButton>
<slot>
<div class="header">
<slot name="header" />
<slot name="header"></slot>
</div>
<div class="content">
<slot name="text" />
<slot name="text"></slot>
</div>
<div class="actions">
<x-button
@click="$emit('close')"
variant="tertiary"
class="has-text-danger"
@click="$emit('close')"
>
{{ $t('misc.cancel') }}
</x-button>
<x-button
v-cy="'modalPrimary'"
variant="primary"
:shadow="false"
@click="$emit('submit')"
variant="primary"
v-cy="'modalPrimary'"
:shadow="false"
>
{{ $t('misc.doit') }}
</x-button>

View File

@ -1,15 +1,8 @@
<template>
<div class="no-auth-wrapper">
<Logo
class="logo"
width="200"
height="58"
/>
<Logo class="logo" width="200" height="58"/>
<div class="noauth-container">
<section
class="image"
:class="{'has-message': motd !== ''}"
>
<section class="image" :class="{'has-message': motd !== ''}">
<Message v-if="motd !== ''">
{{ motd }}
</Message>
@ -19,22 +12,14 @@
</section>
<section class="content">
<div>
<h2
v-if="title"
class="title"
>
{{ title }}
</h2>
<ApiConfig v-if="showApiConfig" />
<Message
v-if="motd !== ''"
class="is-hidden-tablet mb-4"
>
<h2 class="title" v-if="title">{{ title }}</h2>
<api-config v-if="showApiConfig"/>
<Message v-if="motd !== ''" class="is-hidden-tablet mb-4">
{{ motd }}
</Message>
<slot />
<slot/>
</div>
<Legal />
<legal/>
</section>
</div>
</div>
@ -53,11 +38,6 @@ import ApiConfig from '@/components/misc/api-config.vue'
import {useTitle} from '@/composables/useTitle'
import {useConfigStore} from '@/stores/config'
const {
showApiConfig = true,
} = defineProps<{
showApiConfig?: boolean
}>()
const configStore = useConfigStore()
const motd = computed(() => configStore.motd)
@ -66,6 +46,11 @@ const {t} = useI18n({useScope: 'global'})
const title = computed(() => t(route.meta?.title as string || ''))
useTitle(() => title.value)
const {
showApiConfig = true,
} = defineProps<{
showApiConfig?: boolean
}>()
</script>
<style lang="scss" scoped>

View File

@ -1,5 +1,5 @@
<template>
<p class="has-text-centered has-text-grey is-italic p-4 mb-4">
<slot />
<slot></slot>
</p>
</template>

View File

@ -1,43 +1,30 @@
<template>
<notifications
position="bottom left"
:max="2"
class="global-notification"
>
<notifications position="bottom left" :max="2" class="global-notification">
<template #body="{ item, close }">
<!-- FIXME: overlay whole notification with button and add event listener on that button instead -->
<div
class="vue-notification-template vue-notification"
:class="[
'vue-notification-template',
'vue-notification',
item.type,
]"
@click="close()"
>
<div
v-if="item.title"
class="notification-title"
>
{{ item.title }}
</div>
<div v-if="item.title" class="notification-title">{{ item.title }}</div>
<div class="notification-content">
<template
v-for="(t, k) in item.text"
:key="k"
>
{{ t }}<br>
</template>
<template v-for="(t, k) in item.text" :key="k">{{ t }}<br /></template>
</div>
<div
v-if="item.data?.actions?.length > 0"
class="buttons is-right"
v-if="item.data?.actions?.length > 0"
>
<x-button
v-for="(action, i) in item.data.actions"
:key="'action_' + i"
@click="action.callback"
:shadow="false"
class="is-small"
variant="secondary"
@click="action.callback"
v-for="(action, i) in item.data.actions"
>
{{ action.title }}
</x-button>

View File

@ -1,33 +1,25 @@
<template>
<nav
v-if="totalPages > 1"
aria-label="pagination"
class="pagination is-centered p-4"
role="navigation"
v-if="totalPages > 1"
>
<router-link
:disabled="currentPage === 1 || undefined"
:to="getRouteForPagination(currentPage - 1)"
class="pagination-previous"
>
class="pagination-previous">
{{ $t('misc.previous') }}
</router-link>
<router-link
:disabled="currentPage === totalPages || undefined"
:to="getRouteForPagination(currentPage + 1)"
class="pagination-next"
>
class="pagination-next">
{{ $t('misc.next') }}
</router-link>
<ul class="pagination-list">
<li
v-for="(p, i) in pages"
:key="`page-${i}`"
>
<span
v-if="p.isEllipsis"
class="pagination-ellipsis"
>&hellip;</span>
<li :key="`page-${i}`" v-for="(p, i) in pages">
<span class="pagination-ellipsis" v-if="p.isEllipsis">&hellip;</span>
<router-link
v-else
class="pagination-link"
@ -45,17 +37,6 @@
<script lang="ts" setup>
import {computed} from 'vue'
const props = defineProps({
totalPages: {
type: Number,
required: true,
},
currentPage: {
type: Number,
default: 0,
},
})
function createPagination(totalPages: number, currentPage: number) {
const pages = []
for (let i = 0; i < totalPages; i++) {
@ -100,6 +81,17 @@ function getRouteForPagination(page = 1, type = null) {
}
}
const props = defineProps({
totalPages: {
type: Number,
required: true,
},
currentPage: {
type: Number,
default: 0,
},
})
const pages = computed(() => createPagination(props.totalPages, props.currentPage))
</script>

View File

@ -1,24 +1,14 @@
<template>
<slot
name="trigger"
:is-open="open"
:toggle="toggle"
:close="close"
/>
<slot name="trigger" :isOpen="open" :toggle="toggle" :close="close"></slot>
<div
ref="popup"
class="popup"
:class="{
'is-open': open,
'has-overflow': props.hasOverflow && open
}"
ref="popup"
>
<slot
name="content"
:is-open="open"
:toggle="toggle"
:close="close"
/>
<slot name="content" :isOpen="open" :toggle="toggle" :close="close"/>
</div>
</template>

View File

@ -1,55 +1,37 @@
<template>
<!-- This is a workaround to get the sw to "see" the to-be-cached version of the offline background image -->
<div
class="offline"
style="height: 0;width: 0;"
/>
<div
v-if="!online"
class="app offline"
>
<div class="offline" style="height: 0;width: 0;"></div>
<div class="app offline" v-if="!online">
<div class="offline-message">
<h1 class="title">
{{ $t('offline.title') }}
</h1>
<h1 class="title">{{ $t('offline.title') }}</h1>
<p>{{ $t('offline.text') }}</p>
</div>
</div>
<template v-else-if="ready">
<slot />
<slot/>
</template>
<section v-else-if="error !== ''">
<NoAuthWrapper :show-api-config="false">
<no-auth-wrapper :show-api-config="false">
<p v-if="error === ERROR_NO_API_URL">
{{ $t('ready.noApiUrlConfigured') }}
</p>
<Message
v-else
variant="danger"
class="mb-4"
>
<message variant="danger" v-else class="mb-4">
<p>
{{ $t('ready.errorOccured') }}<br>
{{ $t('ready.errorOccured') }}<br/>
{{ error }}
</p>
<p>
{{ $t('ready.checkApiUrl') }}
</p>
</Message>
<ApiConfig
:configure-open="true"
@foundApi="load"
/>
</NoAuthWrapper>
</message>
<api-config :configure-open="true" @found-api="load"/>
</no-auth-wrapper>
</section>
<CustomTransition name="fade">
<section
v-if="showLoading"
class="vikunja-loading"
>
<Logo class="logo" />
<section class="vikunja-loading" v-if="showLoading">
<Logo class="logo"/>
<p>
<span class="loader-container is-loading-small is-loading" />
<span class="loader-container is-loading-small is-loading"></span>
{{ $t('ready.loading') }}
</p>
</section>

View File

@ -1,12 +1,6 @@
<template>
<component
:is="is"
class="shortcuts"
>
<template
v-for="(k, i) in keys"
:key="i"
>
<component :is="is" class="shortcuts">
<template v-for="(k, i) in keys" :key="i">
<kbd>{{ k }}</kbd>
<span v-if="i < keys.length - 1">{{ combination }}</span>
</template>

View File

@ -1,32 +1,32 @@
<template>
<x-button
v-if="type === 'button'"
v-tooltip="tooltipText"
variant="secondary"
:icon="iconName"
:disabled="disabled"
v-tooltip="tooltipText"
@click="changeSubscription"
:disabled="disabled"
>
{{ buttonText }}
</x-button>
<DropdownItem
v-else-if="type === 'dropdown'"
v-tooltip="tooltipText"
@click="changeSubscription"
:disabled="disabled"
:icon="iconName"
@click="changeSubscription"
>
{{ buttonText }}
</DropdownItem>
<BaseButton
v-else
v-tooltip="tooltipText"
@click="changeSubscription"
:class="{'is-disabled': disabled}"
:disabled="disabled"
@click="changeSubscription"
>
<span class="icon">
<icon :icon="iconName" />
<icon :icon="iconName"/>
</span>
{{ buttonText }}
</BaseButton>
@ -63,10 +63,10 @@ const props = defineProps({
},
})
const emit = defineEmits(['update:modelValue'])
const subscriptionEntity = computed<string | null>(() => props.modelValue?.entity ?? null)
const emit = defineEmits(['update:modelValue'])
const subscriptionService = shallowRef(new SubscriptionService())
const {t} = useI18n({useScope: 'global'})

View File

@ -4,17 +4,14 @@
:class="{'is-inline': isInline}"
>
<img
v-tooltip="displayName"
:height="avatarSize"
:src="getAvatarUrl(user, avatarSize)"
:width="avatarSize"
:alt="'Avatar of ' + displayName"
class="avatar"
>
<span
v-if="showUsername"
class="username"
>{{ displayName }}</span>
v-tooltip="displayName"
/>
<span class="username" v-if="showUsername">{{ displayName }}</span>
</div>
</template>

View File

@ -1,80 +1,51 @@
<template>
<div class="notifications">
<slot
name="trigger"
toggle-open="() => showNotifications = !showNotifications"
:has-unread-notifications="unreadNotifications > 0"
>
<BaseButton
class="trigger-button"
@click.stop="showNotifications = !showNotifications"
>
<span
v-if="unreadNotifications > 0"
class="unread-indicator"
/>
<icon icon="bell" />
<slot name="trigger" toggleOpen="() => showNotifications = !showNotifications" :has-unread-notifications="unreadNotifications > 0">
<BaseButton class="trigger-button" @click.stop="showNotifications = !showNotifications">
<span class="unread-indicator" v-if="unreadNotifications > 0"></span>
<icon icon="bell"/>
</BaseButton>
</slot>
<CustomTransition name="fade">
<div
v-if="showNotifications"
ref="popup"
class="notifications-list"
>
<div class="notifications-list" v-if="showNotifications" ref="popup">
<span class="head">{{ $t('notification.title') }}</span>
<div
v-for="(n, index) in notifications"
:key="n.id"
class="single-notification"
>
<div
class="read-indicator"
:class="{'read': n.readAt !== null}"
/>
<User
v-if="n.notification.doer"
<div class="read-indicator" :class="{'read': n.readAt !== null}"></div>
<user
:user="n.notification.doer"
:show-username="false"
:avatar-size="16"
v-if="n.notification.doer"
/>
<div class="detail">
<div>
<span
v-if="n.notification.doer"
class="has-text-weight-bold mr-1"
>
<span class="has-text-weight-bold mr-1" v-if="n.notification.doer">
{{ getDisplayName(n.notification.doer) }}
</span>
<BaseButton
class="has-text-left"
@click="() => to(n, index)()"
>
<BaseButton @click="() => to(n, index)()" class="has-text-left">
{{ n.toText(userInfo) }}
</BaseButton>
</div>
<span
v-tooltip="formatDateLong(n.created)"
class="created"
>
<span class="created" v-tooltip="formatDateLong(n.created)">
{{ formatDateSince(n.created) }}
</span>
</div>
</div>
<XButton
<x-button
v-if="notifications.length > 0 && unreadNotifications > 0"
variant="tertiary"
class="mt-2 is-fullwidth"
@click="markAllRead"
variant="tertiary"
class="mt-2 is-fullwidth"
>
{{ $t('notification.markAllRead') }}
</XButton>
<p
v-if="notifications.length === 0"
class="nothing"
>
{{ $t('notification.none') }}<br>
</x-button>
<p class="nothing" v-if="notifications.length === 0">
{{ $t('notification.none') }}<br/>
<span class="explainer">
{{ $t('notification.explainer') }}
</span>
@ -185,8 +156,6 @@ async function markAllRead() {
const notificationService = new NotificationService()
await notificationService.markAllRead()
success({message: t('notification.markAllReadSuccess')})
notifications.value.forEach(n => n.readAt = new Date())
}
</script>

View File

@ -14,7 +14,7 @@
:title="$t('keyboardShortcuts.project.switchToListView')"
class="switch-view-button"
:class="{'is-active': viewName === 'project'}"
:to="{ name: 'project.list', params: { projectId } }"
:to="{ name: 'project.list', params: { projectId } }"
>
{{ $t('project.list.title') }}
</BaseButton>
@ -23,7 +23,7 @@
:title="$t('keyboardShortcuts.project.switchToGanttView')"
class="switch-view-button"
:class="{'is-active': viewName === 'gantt'}"
:to="{ name: 'project.gantt', params: { projectId } }"
:to="{ name: 'project.gantt', params: { projectId } }"
>
{{ $t('project.gantt.title') }}
</BaseButton>
@ -32,7 +32,7 @@
:title="$t('keyboardShortcuts.project.switchToTableView')"
class="switch-view-button"
:class="{'is-active': viewName === 'table'}"
:to="{ name: 'project.table', params: { projectId } }"
:to="{ name: 'project.table', params: { projectId } }"
>
{{ $t('project.table.title') }}
</BaseButton>
@ -49,16 +49,12 @@
<slot name="header" />
</div>
<CustomTransition name="fade">
<Message
v-if="currentProject?.isArchived"
variant="warning"
class="mb-4"
>
<Message variant="warning" v-if="currentProject?.isArchived" class="mb-4">
{{ $t('project.archivedMessage') }}
</Message>
</CustomTransition>
<slot v-if="loadedProjectId" />
<slot v-if="loadedProjectId"/>
</div>
</template>

View File

@ -15,20 +15,11 @@
:class="{'is-visible': background}"
:style="{'background-image': background !== null ? `url(${background})` : undefined}"
/>
<span
v-if="project.isArchived"
class="is-archived"
>{{ $t('project.archived') }}</span>
<span v-if="project.isArchived" class="is-archived" >{{ $t('project.archived') }}</span>
<div
class="project-title"
aria-hidden="true"
>
<span
v-if="project.id < -1"
class="saved-filter-icon icon"
>
<icon icon="filter" />
<div class="project-title" aria-hidden="true">
<span v-if="project.id < -1" class="saved-filter-icon icon">
<icon icon="filter"/>
</span>
{{ project.title }}
</div>

View File

@ -1,13 +1,13 @@
<template>
<ul class="project-grid">
<li
v-for="(item, index) in filteredProjects"
:key="`project_${item.id}_${index}`"
class="project-grid-item"
>
<ProjectCard :project="item" />
</li>
</ul>
<ul class="project-grid">
<li
v-for="(item, index) in filteredProjects"
:key="`project_${item.id}_${index}`"
class="project-grid-item"
>
<ProjectCard :project="item" />
</li>
</ul>
</template>
<script lang="ts" setup>

View File

@ -7,9 +7,9 @@
{{ $t('filters.clear') }}
</x-button>
<x-button
@click="() => modalOpen = true"
variant="secondary"
icon="filter"
@click="() => modalOpen = true"
>
{{ $t('filters.title') }}
</x-button>
@ -20,10 +20,10 @@
variant="hint-modal"
@close="() => modalOpen = false"
>
<Filters
ref="filters"
v-model="value"
<filters
:has-title="true"
v-model="value"
ref="filters"
class="filter-popup"
/>
</modal>

View File

@ -1,157 +1,134 @@
<template>
<card
class="filters has-overflow"
:title="hasTitle ? $t('filters.title') : ''"
>
<card class="filters has-overflow" :title="hasTitle ? $t('filters.title') : ''">
<div class="field is-flex is-flex-direction-column">
<Fancycheckbox
<fancycheckbox
v-model="params.filter_include_nulls"
@update:modelValue="change()"
@update:model-value="change()"
>
{{ $t('filters.attributes.includeNulls') }}
</Fancycheckbox>
<Fancycheckbox
</fancycheckbox>
<fancycheckbox
v-model="filters.requireAllFilters"
@update:modelValue="setFilterConcat()"
@update:model-value="setFilterConcat()"
>
{{ $t('filters.attributes.requireAll') }}
</Fancycheckbox>
<Fancycheckbox
</fancycheckbox>
<fancycheckbox
v-model="filters.done"
@update:modelValue="setDoneFilter"
@update:model-value="setDoneFilter"
>
{{ $t('filters.attributes.showDoneTasks') }}
</Fancycheckbox>
<Fancycheckbox
</fancycheckbox>
<fancycheckbox
v-if="!['project.kanban', 'project.table'].includes($route.name as string)"
v-model="sortAlphabetically"
@update:modelValue="change()"
@update:model-value="change()"
>
{{ $t('filters.attributes.sortAlphabetically') }}
</Fancycheckbox>
</fancycheckbox>
</div>
<div class="field">
<label class="label">{{ $t('misc.search') }}</label>
<div class="control">
<input
v-model="params.s"
class="input"
:placeholder="$t('misc.search')"
v-model="params.s"
@blur="change()"
@keyup.enter="change()"
>
/>
</div>
</div>
<div class="field">
<label class="label">{{ $t('task.attributes.priority') }}</label>
<div class="control single-value-control">
<PrioritySelect
<priority-select
v-model.number="filters.priority"
@update:model-value="setPriority"
:disabled="!filters.usePriority || undefined"
@update:modelValue="setPriority"
/>
<Fancycheckbox
<fancycheckbox
v-model="filters.usePriority"
@update:modelValue="setPriority"
@update:model-value="setPriority"
>
{{ $t('filters.attributes.enablePriority') }}
</Fancycheckbox>
</fancycheckbox>
</div>
</div>
<div class="field">
<label class="label">{{ $t('task.attributes.percentDone') }}</label>
<div class="control single-value-control">
<PercentDoneSelect
<percent-done-select
v-model.number="filters.percentDone"
@update:model-value="setPercentDoneFilter"
:disabled="!filters.usePercentDone || undefined"
@update:modelValue="setPercentDoneFilter"
/>
<Fancycheckbox
<fancycheckbox
v-model="filters.usePercentDone"
@update:modelValue="setPercentDoneFilter"
@update:model-value="setPercentDoneFilter"
>
{{ $t('filters.attributes.enablePercentDone') }}
</Fancycheckbox>
</fancycheckbox>
</div>
</div>
<div class="field">
<label class="label">{{ $t('task.attributes.dueDate') }}</label>
<div class="control">
<DatepickerWithRange
<datepicker-with-range
v-model="filters.dueDate"
@update:modelValue="values => setDateFilter('due_date', values)"
@update:model-value="values => setDateFilter('due_date', values)"
>
<template #trigger="{toggle, buttonText}">
<x-button
variant="secondary"
:shadow="false"
class="mb-2"
@click.prevent.stop="toggle()"
>
<x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2">
{{ buttonText }}
</x-button>
</template>
</DatepickerWithRange>
</datepicker-with-range>
</div>
</div>
<div class="field">
<label class="label">{{ $t('task.attributes.startDate') }}</label>
<div class="control">
<DatepickerWithRange
<datepicker-with-range
v-model="filters.startDate"
@update:modelValue="values => setDateFilter('start_date', values)"
@update:model-value="values => setDateFilter('start_date', values)"
>
<template #trigger="{toggle, buttonText}">
<x-button
variant="secondary"
:shadow="false"
class="mb-2"
@click.prevent.stop="toggle()"
>
<x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2">
{{ buttonText }}
</x-button>
</template>
</DatepickerWithRange>
</datepicker-with-range>
</div>
</div>
<div class="field">
<label class="label">{{ $t('task.attributes.endDate') }}</label>
<div class="control">
<DatepickerWithRange
<datepicker-with-range
v-model="filters.endDate"
@update:modelValue="values => setDateFilter('end_date', values)"
@update:model-value="values => setDateFilter('end_date', values)"
>
<template #trigger="{toggle, buttonText}">
<x-button
variant="secondary"
:shadow="false"
class="mb-2"
@click.prevent.stop="toggle()"
>
<x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2">
{{ buttonText }}
</x-button>
</template>
</DatepickerWithRange>
</datepicker-with-range>
</div>
</div>
<div class="field">
<label class="label">{{ $t('task.attributes.reminders') }}</label>
<div class="control">
<DatepickerWithRange
<datepicker-with-range
v-model="filters.reminders"
@update:modelValue="values => setDateFilter('reminders', values)"
@update:model-value="values => setDateFilter('reminders', values)"
>
<template #trigger="{toggle, buttonText}">
<x-button
variant="secondary"
:shadow="false"
class="mb-2"
@click.prevent.stop="toggle()"
>
<x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2">
{{ buttonText }}
</x-button>
</template>
</DatepickerWithRange>
</datepicker-with-range>
</div>
</div>
@ -169,10 +146,10 @@
<div class="field">
<label class="label">{{ $t('task.attributes.labels') }}</label>
<div class="control labels-list">
<EditLabels
v-model="entities.labels"
<edit-labels
:creatable="false"
@update:modelValue="changeLabelFilter"
v-model="entities.labels"
@update:model-value="changeLabelFilter"
/>
</div>
</div>
@ -185,9 +162,9 @@
<div class="control">
<SelectProject
v-model="entities.projects"
:project-filter="p => p.id > 0"
@select="changeMultiselectFilter('projects', 'project_id')"
@remove="changeMultiselectFilter('projects', 'project_id')"
:project-filter="p => p.id > 0"
/>
</div>
</div>
@ -228,18 +205,6 @@ import ProjectService from '@/services/project'
// FIXME: do not use this here for now. instead create new version from DEFAULT_PARAMS
import {getDefaultParams} from '@/composables/useTaskList'
const props = defineProps({
modelValue: {
required: true,
},
hasTitle: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['update:modelValue'])
// FIXME: merge with DEFAULT_PARAMS in taskProject.js
const DEFAULT_PARAMS = {
sort_by: [],
@ -268,6 +233,18 @@ const DEFAULT_FILTERS = {
project_id: '',
} as const
const props = defineProps({
modelValue: {
required: true,
},
hasTitle: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['update:modelValue'])
const {modelValue} = toRefs(props)
const labelStore = useLabelStore()

View File

@ -1,108 +1,99 @@
<template>
<Dropdown>
<dropdown>
<template #trigger="triggerProps">
<slot
name="trigger"
v-bind="triggerProps"
>
<BaseButton
class="dropdown-trigger"
@click="triggerProps.toggleOpen"
>
<icon
icon="ellipsis-h"
class="icon"
/>
<slot name="trigger" v-bind="triggerProps">
<BaseButton class="dropdown-trigger" @click="triggerProps.toggleOpen">
<icon icon="ellipsis-h" class="icon"/>
</BaseButton>
</slot>
</template>
<template v-if="isSavedFilter(project)">
<DropdownItem
<dropdown-item
:to="{ name: 'filter.settings.edit', params: { projectId: project.id } }"
icon="pen"
>
{{ $t('menu.edit') }}
</DropdownItem>
<DropdownItem
</dropdown-item>
<dropdown-item
:to="{ name: 'filter.settings.delete', params: { projectId: project.id } }"
icon="trash-alt"
>
{{ $t('misc.delete') }}
</DropdownItem>
</dropdown-item>
</template>
<template v-else-if="project.isArchived">
<DropdownItem
<dropdown-item
:to="{ name: 'project.settings.archive', params: { projectId: project.id } }"
icon="archive"
>
{{ $t('menu.unarchive') }}
</DropdownItem>
</dropdown-item>
</template>
<template v-else>
<DropdownItem
<dropdown-item
:to="{ name: 'project.settings.edit', params: { projectId: project.id } }"
icon="pen"
>
{{ $t('menu.edit') }}
</DropdownItem>
<DropdownItem
</dropdown-item>
<dropdown-item
v-if="backgroundsEnabled"
:to="{ name: 'project.settings.background', params: { projectId: project.id } }"
icon="image"
>
{{ $t('menu.setBackground') }}
</DropdownItem>
<DropdownItem
</dropdown-item>
<dropdown-item
:to="{ name: 'project.settings.share', params: { projectId: project.id } }"
icon="share-alt"
>
{{ $t('menu.share') }}
</DropdownItem>
<DropdownItem
</dropdown-item>
<dropdown-item
:to="{ name: 'project.settings.duplicate', params: { projectId: project.id } }"
icon="paste"
>
{{ $t('menu.duplicate') }}
</DropdownItem>
<DropdownItem
</dropdown-item>
<dropdown-item
:to="{ name: 'project.settings.archive', params: { projectId: project.id } }"
icon="archive"
>
{{ $t('menu.archive') }}
</DropdownItem>
</dropdown-item>
<Subscription
class="has-no-shadow"
:is-button="false"
entity="project"
:entity-id="project.id"
:model-value="project.subscription"
@update:model-value="setSubscriptionInStore"
type="dropdown"
@update:modelValue="setSubscriptionInStore"
/>
<DropdownItem
<dropdown-item
:to="{ name: 'project.settings.webhooks', params: { projectId: project.id } }"
icon="bolt"
>
{{ $t('project.webhooks.title') }}
</DropdownItem>
<DropdownItem
</dropdown-item>
<dropdown-item
v-if="level < 2"
:to="{ name: 'project.createFromParent', params: { parentProjectId: project.id } }"
icon="layer-group"
>
{{ $t('menu.createProject') }}
</DropdownItem>
<DropdownItem
</dropdown-item>
<dropdown-item
:to="{ name: 'project.settings.delete', params: { projectId: project.id } }"
icon="trash-alt"
class="has-text-danger"
>
{{ $t('menu.delete') }}
</DropdownItem>
</dropdown-item>
</template>
</Dropdown>
</dropdown>
</template>
<script setup lang="ts">

View File

@ -1,53 +1,33 @@
<template>
<modal
:enabled="active"
:overflow="isNewTaskCommand"
@close="closeQuickActions"
>
<modal :enabled="active" @close="closeQuickActions" :overflow="isNewTaskCommand">
<div class="card quick-actions">
<div
class="action-input"
:class="{'has-active-cmd': selectedCmd !== null}"
>
<div
v-if="selectedCmd !== null"
class="active-cmd tag"
>
<div class="action-input" :class="{'has-active-cmd': selectedCmd !== null}">
<div class="active-cmd tag" v-if="selectedCmd !== null">
{{ selectedCmd.title }}
</div>
<input
ref="searchInput"
v-model="query"
v-focus
class="input"
:class="{'is-loading': loading}"
v-model="query"
:placeholder="placeholder"
@keyup="search"
ref="searchInput"
@keydown.down.prevent="select(0, 0)"
@keyup.prevent.delete="unselectCmd"
@keyup.prevent.enter="doCmd"
@keyup.prevent.esc="closeQuickActions"
>
/>
</div>
<div
v-if="hintText !== '' && !isNewTaskCommand"
class="help has-text-grey-light p-2"
>
<div class="help has-text-grey-light p-2" v-if="hintText !== '' && !isNewTaskCommand">
{{ hintText }}
</div>
<QuickAddMagic v-if="isNewTaskCommand" />
<quick-add-magic v-if="isNewTaskCommand"/>
<div
v-if="selectedCmd === null"
class="results"
>
<div
v-for="(r, k) in results"
:key="k"
class="result"
>
<div class="results" v-if="selectedCmd === null">
<div v-for="(r, k) in results" :key="k" class="result">
<span class="result-title">
{{ r.title }}
</span>
@ -55,9 +35,9 @@
<BaseButton
v-for="(i, key) in r.items"
:key="key"
:ref="(el: Element | ComponentPublicInstance | null) => setResultRefs(el, k, key)"
class="result-item-button"
:class="{'is-strikethrough': (i as DoAction<ITask>)?.done}"
:ref="(el: Element | ComponentPublicInstance | null) => setResultRefs(el, k, key)"
@keydown.up.prevent="select(k, key - 1)"
@keydown.down.prevent="select(k, key + 1)"
@click.prevent.stop="doAction(r.type, i)"
@ -65,10 +45,10 @@
@keyup.prevent.esc="searchInput?.focus()"
>
<template v-if="r.type === ACTION_TYPE.LABELS">
<XLabel :label="i" />
<x-label :label="i"/>
</template>
<template v-else-if="r.type === ACTION_TYPE.TASK">
<SingleTaskInlineReadonly
<single-task-inline-readonly
:task="i"
:show-project="true"
/>

View File

@ -3,9 +3,8 @@
<p class="has-text-weight-bold">
{{ $t('project.share.links.title') }}
<span
v-tooltip="$t('project.share.links.explanation')"
class="is-size-7 has-text-grey is-italic ml-3"
>
v-tooltip="$t('project.share.links.explanation')">
{{ $t('project.share.links.what') }}
</span>
</p>
@ -13,30 +12,20 @@
<div class="sharables-project">
<x-button
v-if="!(linkShares.length === 0 || showNewForm)"
icon="plus"
class="mb-4"
@click="showNewForm = true"
>
icon="plus"
class="mb-4">
{{ $t('project.share.links.create') }}
</x-button>
<div
v-if="linkShares.length === 0 || showNewForm"
class="p-4"
>
<div class="p-4" v-if="linkShares.length === 0 || showNewForm">
<div class="field">
<label
class="label"
for="linkShareRight"
>
<label class="label" for="linkShareRight">
{{ $t('project.share.right.title') }}
</label>
<div class="control">
<div class="select">
<select
id="linkShareRight"
v-model="selectedRight"
>
<select v-model="selectedRight" id="linkShareRight">
<option :value="RIGHTS.READ">
{{ $t('project.share.right.read') }}
</option>
@ -51,150 +40,131 @@
</div>
</div>
<div class="field">
<label
class="label"
for="linkShareName"
>
<label class="label" for="linkShareName">
{{ $t('project.share.links.name') }}
</label>
<div class="control">
<input
id="linkShareName"
v-model="name"
v-tooltip="$t('project.share.links.nameExplanation')"
class="input"
:placeholder="$t('project.share.links.namePlaceholder')"
>
v-tooltip="$t('project.share.links.nameExplanation')"
v-model="name"
/>
</div>
</div>
<div class="field">
<label
class="label"
for="linkSharePassword"
>
<label class="label" for="linkSharePassword">
{{ $t('project.share.links.password') }}
</label>
<div class="control">
<input
id="linkSharePassword"
v-model="password"
v-tooltip="$t('project.share.links.passwordExplanation')"
type="password"
class="input"
:placeholder="$t('user.auth.passwordPlaceholder')"
>
v-tooltip="$t('project.share.links.passwordExplanation')"
v-model="password"
/>
</div>
</div>
<x-button
icon="plus"
@click="add(projectId)"
>
<x-button @click="add(projectId)" icon="plus">
{{ $t('project.share.share') }}
</x-button>
</div>
<table
v-if="linkShares.length > 0"
class="table has-actions is-striped is-hoverable is-fullwidth"
v-if="linkShares.length > 0"
>
<thead>
<tr>
<th />
<th>{{ $t('project.share.links.view') }}</th>
<th>{{ $t('project.share.attributes.delete') }}</th>
</tr>
<tr>
<th></th>
<th>{{ $t('project.share.links.view') }}</th>
<th>{{ $t('project.share.attributes.delete') }}</th>
</tr>
</thead>
<tbody>
<tr
v-for="s in linkShares"
:key="s.id"
>
<td>
<p
v-if="s.name !== ''"
class="mb-2 is-italic"
>
{{ s.name }}
</p>
<tr :key="s.id" v-for="s in linkShares">
<td>
<p class="mb-2 is-italic" v-if="s.name !== ''">
{{ s.name }}
</p>
<p class="mb-2">
<i18n-t
keypath="project.share.links.sharedBy"
scope="global"
>
<strong>{{ getDisplayName(s.sharedBy) }}</strong>
</i18n-t>
</p>
<p class="mb-2">
<i18n-t keypath="project.share.links.sharedBy" scope="global">
<strong>{{ getDisplayName(s.sharedBy) }}</strong>
</i18n-t>
</p>
<p class="mb-2">
<template v-if="s.right === RIGHTS.ADMIN">
<span class="icon is-small">
<icon icon="lock" />
</span>&nbsp;
{{ $t('project.share.right.admin') }}
</template>
<template v-else-if="s.right === RIGHTS.READ_WRITE">
<span class="icon is-small">
<icon icon="pen" />
</span>&nbsp;
{{ $t('project.share.right.readWrite') }}
</template>
<template v-else>
<span class="icon is-small">
<icon icon="users" />
</span>&nbsp;
{{ $t('project.share.right.read') }}
</template>
</p>
<p class="mb-2">
<template v-if="s.right === RIGHTS.ADMIN">
<span class="icon is-small">
<icon icon="lock"/>
</span>&nbsp;
{{ $t('project.share.right.admin') }}
</template>
<template v-else-if="s.right === RIGHTS.READ_WRITE">
<span class="icon is-small">
<icon icon="pen"/>
</span>&nbsp;
{{ $t('project.share.right.readWrite') }}
</template>
<template v-else>
<span class="icon is-small">
<icon icon="users"/>
</span>&nbsp;
{{ $t('project.share.right.read') }}
</template>
</p>
<div class="field has-addons no-input-mobile">
<div class="control">
<input
<div class="field has-addons no-input-mobile">
<div class="control">
<input
:value="getShareLink(s.hash, selectedView[s.id])"
class="input"
readonly
type="text"
>
</div>
<div class="control">
<x-button
v-tooltip="$t('misc.copy')"
:shadow="false"
/>
</div>
<div class="control">
<x-button
@click="copy(getShareLink(s.hash, selectedView[s.id]))"
>
<span class="icon">
<icon icon="paste" />
</span>
</x-button>
</div>
:shadow="false"
v-tooltip="$t('misc.copy')"
>
<span class="icon">
<icon icon="paste"/>
</span>
</x-button>
</div>
</td>
<td>
<div class="select">
<select v-model="selectedView[s.id]">
<option
v-for="(title, key) in availableViews"
:key="key"
:value="key"
>
{{ title }}
</option>
</select>
</div>
</td>
<td class="actions">
<x-button
class="is-danger"
icon="trash-alt"
@click="
</div>
</td>
<td>
<div class="select">
<select v-model="selectedView[s.id]">
<option
v-for="(title, key) in availableViews"
:value="key"
:key="key">
{{ title }}
</option>
</select>
</div>
</td>
<td class="actions">
<x-button
@click="
() => {
linkIdToDelete = s.id
showDeleteModal = true
}
"
/>
</td>
</tr>
class="is-danger"
icon="trash-alt"
/>
</td>
</tr>
</tbody>
</table>
</div>
@ -237,7 +207,7 @@ import {useConfigStore} from '@/stores/config'
const props = defineProps({
projectId: {
default: 0,
required: false,
required: true,
},
})

View File

@ -10,119 +10,108 @@
:class="{ 'is-loading': searchService.loading }"
>
<Multiselect
v-model="sharable"
:loading="searchService.loading"
:placeholder="$t('misc.searchPlaceholder')"
@search="find"
:search-results="found"
:label="searchLabel"
@search="find"
v-model="sharable"
/>
</p>
<p class="control">
<x-button @click="add()">
{{ $t('project.share.share') }}
</x-button>
<x-button @click="add()">{{ $t('project.share.share') }}</x-button>
</p>
</div>
</div>
<table
v-if="sharables.length > 0"
class="table has-actions is-striped is-hoverable is-fullwidth mb-4"
>
<table class="table has-actions is-striped is-hoverable is-fullwidth mb-4" v-if="sharables.length > 0">
<tbody>
<tr
v-for="s in sharables"
:key="s.id"
>
<template v-if="shareType === 'user'">
<td>{{ getDisplayName(s) }}</td>
<td>
<template v-if="s.id === userInfo.id">
<b class="is-success">{{ $t('project.share.userTeam.you') }}</b>
</template>
</td>
</template>
<template v-if="shareType === 'team'">
<td>
<router-link
:to="{
<tr :key="s.id" v-for="s in sharables">
<template v-if="shareType === 'user'">
<td>{{ getDisplayName(s) }}</td>
<td>
<template v-if="s.id === userInfo.id">
<b class="is-success">{{ $t('project.share.userTeam.you') }}</b>
</template>
</td>
</template>
<template v-if="shareType === 'team'">
<td>
<router-link
:to="{
name: 'teams.edit',
params: { id: s.id },
}"
>
{{ s.name }}
</router-link>
</td>
</template>
<td class="type">
<template v-if="s.right === RIGHTS.ADMIN">
<span class="icon is-small">
<icon icon="lock" />
</span>
{{ $t('project.share.right.admin') }}
</template>
<template v-else-if="s.right === RIGHTS.READ_WRITE">
<span class="icon is-small">
<icon icon="pen" />
</span>
{{ $t('project.share.right.readWrite') }}
</template>
<template v-else>
<span class="icon is-small">
<icon icon="users" />
</span>
{{ $t('project.share.right.read') }}
</template>
>
{{ s.name }}
</router-link>
</td>
<td
v-if="userIsAdmin"
class="actions"
>
<div class="select">
<select
v-model="selectedRight[s.id]"
class="mr-2"
@change="toggleType(s)"
</template>
<td class="type">
<template v-if="s.right === RIGHTS.ADMIN">
<span class="icon is-small">
<icon icon="lock"/>
</span>
{{ $t('project.share.right.admin') }}
</template>
<template v-else-if="s.right === RIGHTS.READ_WRITE">
<span class="icon is-small">
<icon icon="pen"/>
</span>
{{ $t('project.share.right.readWrite') }}
</template>
<template v-else>
<span class="icon is-small">
<icon icon="users"/>
</span>
{{ $t('project.share.right.read') }}
</template>
</td>
<td class="actions" v-if="userIsAdmin">
<div class="select">
<select
@change="toggleType(s)"
class="mr-2"
v-model="selectedRight[s.id]"
>
<option
:selected="s.right === RIGHTS.READ"
:value="RIGHTS.READ"
>
<option
:selected="s.right === RIGHTS.READ"
:value="RIGHTS.READ"
>
{{ $t('project.share.right.read') }}
</option>
<option
:selected="s.right === RIGHTS.READ_WRITE"
:value="RIGHTS.READ_WRITE"
>
{{ $t('project.share.right.readWrite') }}
</option>
<option
:selected="s.right === RIGHTS.ADMIN"
:value="RIGHTS.ADMIN"
>
{{ $t('project.share.right.admin') }}
</option>
</select>
</div>
<x-button
class="is-danger"
icon="trash-alt"
@click="
{{ $t('project.share.right.read') }}
</option>
<option
:selected="s.right === RIGHTS.READ_WRITE"
:value="RIGHTS.READ_WRITE"
>
{{ $t('project.share.right.readWrite') }}
</option>
<option
:selected="s.right === RIGHTS.ADMIN"
:value="RIGHTS.ADMIN"
>
{{ $t('project.share.right.admin') }}
</option>
</select>
</div>
<x-button
@click="
() => {
sharable = s
showDeleteModal = true
}
"
/>
</td>
</tr>
class="is-danger"
icon="trash-alt"
/>
</td>
</tr>
</tbody>
</table>
<Nothing v-else>
<nothing v-else>
{{ $t('project.share.userTeam.notShared', {type: shareTypeNames}) }}
</Nothing>
</nothing>
<modal
:enabled="showDeleteModal"
@ -131,8 +120,8 @@
>
<template #header>
<span>{{
$t('project.share.userTeam.removeHeader', {type: shareTypeName, sharable: sharableName})
}}</span>
$t('project.share.userTeam.removeHeader', {type: shareTypeName, sharable: sharableName})
}}</span>
</template>
<template #text>
<p>{{ $t('project.share.userTeam.removeText', {type: shareTypeName, sharable: sharableName}) }}</p>
@ -142,7 +131,7 @@
</template>
<script lang="ts">
export default {name: 'UserTeamShare'}
export default {name: 'userTeamShare'}
</script>
<script setup lang="ts">

View File

@ -3,11 +3,7 @@
v-if="props.isLoading && !ganttBars.length || dayjsLanguageLoading"
class="gantt-container"
/>
<div
v-else
ref="ganttContainer"
class="gantt-container"
>
<div ref="ganttContainer" class="gantt-container" v-else>
<GGanttChart
:date-format="DAYJS_ISO_DATE_FORMAT"
:chart-start="isoToKebabDate(filters.dateFrom)"
@ -16,9 +12,9 @@
bar-start="startDate"
bar-end="endDate"
:grid="true"
@dragend-bar="updateGanttTask"
@dblclick-bar="openTask"
:width="ganttChartWidth"
@dragendBar="updateGanttTask"
@dblclickBar="openTask"
>
<template #timeunit="{value, date}">
<div
@ -76,14 +72,14 @@ export interface GanttChartProps {
defaultTaskEndDate: DateISO
}
const DAYJS_ISO_DATE_FORMAT = 'YYYY-MM-DD'
const props = defineProps<GanttChartProps>()
const emit = defineEmits<{
(e: 'update:task', task: ITaskPartialWithId): void
}>()
const DAYJS_ISO_DATE_FORMAT = 'YYYY-MM-DD'
const {tasks, filters} = toRefs(props)
// setup dayjs for vue-ganttastic
@ -127,23 +123,6 @@ watch(
function transformTaskToGanttBar(t: ITask) {
const black = 'var(--grey-800)'
const taskColor = getHexColor(t.hexColor)
let textColor = black
let backgroundColor = 'var(--grey-100)'
if(t.startDate) {
backgroundColor = taskColor ?? ''
if(typeof taskColor === 'undefined') {
textColor = 'white'
backgroundColor = 'var(--primary)'
} else if(colorIsDark(taskColor)) {
textColor = black
} else {
textColor = 'white'
}
}
return [{
startDate: isoToKebabDate(t.startDate ? t.startDate.toISOString() : props.defaultTaskStartDate),
endDate: isoToKebabDate(t.endDate ? t.endDate.toISOString() : props.defaultTaskEndDate),
@ -152,8 +131,8 @@ function transformTaskToGanttBar(t: ITask) {
label: t.title,
hasHandles: true,
style: {
color: textColor,
backgroundColor,
color: t.startDate ? (colorIsDark(getHexColor(t.hexColor)) ? black : 'white') : black,
backgroundColor: t.startDate ? getHexColor(t.hexColor) : 'var(--grey-100)',
border: t.startDate ? '' : '2px dashed var(--grey-300)',
'text-decoration': t.done ? 'line-through' : null,
},

View File

@ -1,24 +1,20 @@
<template>
<form
class="add-new-task"
@submit.prevent="createTask"
class="add-new-task"
>
<CustomTransition name="width">
<input
v-if="newTaskFieldActive"
ref="newTaskTitleField"
v-model="newTaskTitle"
class="input"
type="text"
@blur="hideCreateNewTask"
@keyup.esc="newTaskFieldActive = false"
>
class="input"
ref="newTaskTitleField"
type="text"
/>
</CustomTransition>
<x-button
:shadow="false"
icon="plus"
@click="showCreateTaskOrCreate"
>
<x-button @click="showCreateTaskOrCreate" :shadow="false" icon="plus">
{{ $t('task.new') }}
</x-button>
</form>
@ -31,7 +27,7 @@ import type {ITask} from '@/modelTypes/ITask'
import CustomTransition from '@/components/misc/CustomTransition.vue'
const emit = defineEmits<{
(e: 'createTask', title: string): Promise<ITask>
(e: 'create-task', title: string): Promise<ITask>
}>()
const newTaskFieldActive = ref(false)
@ -60,7 +56,7 @@ async function createTask() {
if (!newTaskFieldActive.value) {
return
}
await emit('createTask', newTaskTitle.value)
await emit('create-task', newTaskTitle.value)
newTaskTitle.value = ''
hideCreateNewTask()
}

View File

@ -1,34 +1,31 @@
<template>
<div
ref="taskAdd"
class="task-add"
>
<div class="task-add" ref="taskAdd">
<div class="add-task__field field is-grouped">
<p class="control has-icons-left has-icons-right is-expanded">
<textarea
ref="newTaskInput"
v-model="newTaskTitle"
v-focus
class="add-task-textarea input"
:class="{'textarea-empty': newTaskTitle === ''}"
:placeholder="$t('project.list.addPlaceholder')"
rows="1"
v-focus
v-model="newTaskTitle"
ref="newTaskInput"
@keyup="resetEmptyTitleError"
@keydown.enter="handleEnter"
/>
<span class="icon is-small is-left">
<icon icon="tasks" />
<icon icon="tasks"/>
</span>
<QuickAddMagic :highlight-hint-icon="taskAddHovered" />
<quick-add-magic :highlight-hint-icon="taskAddHovered"/>
</p>
<p class="control">
<x-button
class="add-task-button"
:disabled="newTaskTitle === '' || loading || undefined"
@click="addTask()"
icon="plus"
:loading="loading"
:aria-label="$t('project.list.add')"
@click="addTask()"
>
<span class="button-text">
{{ $t('project.list.add') }}
@ -37,10 +34,7 @@
</p>
</div>
<Expandable :open="errorMessage !== ''">
<p
v-if="errorMessage !== ''"
class="pt-3 mt-0 help is-danger"
>
<p class="pt-3 mt-0 help is-danger" v-if="errorMessage !== ''">
{{ errorMessage }}
</p>
</Expandable>

View File

@ -22,29 +22,26 @@ const hasDelete = computed(() => typeof remove !== 'undefined' && !disabled)
</script>
<template>
<div
class="assignees-list"
:class="{'is-inline': inline}"
>
<div class="assignees-list" :class="{'is-inline': inline}">
<span
v-for="user in assignees"
:key="user.id"
class="assignee"
:key="user.id"
>
<User
:key="'user'+user.id"
:avatar-size="avatarSize"
:show-username="false"
:user="user"
:class="{'m-2': hasDelete}"
:class="{'m-2': hasDelete, 'mr-3': !hasDelete}"
/>
<BaseButton
v-if="hasDelete"
:key="'delete'+user.id"
class="remove-assignee"
v-if="hasDelete"
@click="remove(user)"
class="remove-assignee"
>
<icon icon="times" />
<icon icon="times"/>
</BaseButton>
</span>
</div>

View File

@ -2,38 +2,37 @@
<div class="attachments">
<h3>
<span class="icon is-grey">
<icon icon="paperclip" />
<icon icon="paperclip"/>
</span>
{{ $t('task.attachment.title') }}
</h3>
<input
v-if="editEnabled"
id="files"
ref="filesRef"
:disabled="loading || undefined"
multiple
type="file"
@change="uploadNewAttachment()"
>
<ProgressBar
v-if="attachmentService.uploadProgress > 0"
:value="attachmentService.uploadProgress * 100"
is-primary
id="files"
multiple
ref="filesRef"
type="file"
/>
<div
v-if="attachments.length > 0"
class="files"
<progress
v-if="attachmentService.uploadProgress > 0"
:value="attachmentService.uploadProgress"
class="progress is-primary"
max="100"
>
{{ attachmentService.uploadProgress }}%
</progress>
<div class="files" v-if="attachments.length > 0">
<!-- FIXME: don't use a for element that wraps other links / buttons
Instead: overlay element with button that is inside.
-->
<a
class="attachment"
v-for="a in attachments"
:key="a.id"
class="attachment"
@click="viewOrDownload(a)"
>
<div class="filename">
@ -47,10 +46,7 @@
</div>
<div class="info">
<p class="attachment-info-meta">
<i18n-t
keypath="task.attachment.createdBy"
scope="global"
>
<i18n-t keypath="task.attachment.createdBy" scope="global">
<span v-tooltip="formatDateLong(a.created)">
{{ formatDateSince(a.created) }}
</span>
@ -69,24 +65,24 @@
</p>
<p>
<BaseButton
v-tooltip="$t('task.attachment.downloadTooltip')"
class="attachment-info-meta-button"
@click.prevent.stop="downloadAttachment(a)"
v-tooltip="$t('task.attachment.downloadTooltip')"
>
{{ $t('misc.download') }}
</BaseButton>
<BaseButton
v-tooltip="$t('task.attachment.copyUrlTooltip')"
class="attachment-info-meta-button"
@click.stop="copyUrl(a)"
v-tooltip="$t('task.attachment.copyUrlTooltip')"
>
{{ $t('task.attachment.copyUrl') }}
</BaseButton>
<BaseButton
v-if="editEnabled"
v-tooltip="$t('task.attachment.deleteTooltip')"
class="attachment-info-meta-button"
@click.prevent.stop="setAttachmentToDelete(a)"
v-tooltip="$t('task.attachment.deleteTooltip')"
>
{{ $t('misc.delete') }}
</BaseButton>
@ -109,11 +105,11 @@
<x-button
v-if="editEnabled"
:disabled="loading"
@click="filesRef?.click()"
class="mb-4"
icon="cloud-upload-alt"
variant="secondary"
:shadow="false"
@click="filesRef?.click()"
>
{{ $t('task.attachment.upload') }}
</x-button>
@ -121,17 +117,15 @@
<!-- Dropzone -->
<Teleport to="body">
<div
v-if="editEnabled"
:class="{ hidden: !isOverDropZone }"
class="dropzone"
v-if="editEnabled"
>
<div class="drop-hint">
<div class="icon">
<icon icon="cloud-upload-alt" />
</div>
<div class="hint">
{{ $t('task.attachment.drop') }}
<icon icon="cloud-upload-alt"/>
</div>
<div class="hint">{{ $t('task.attachment.drop') }}</div>
</div>
</div>
</Teleport>
@ -148,7 +142,7 @@
<template #text>
<p>
{{ $t('task.attachment.deleteText1', {filename: attachmentToDelete.file.name}) }}<br>
{{ $t('task.attachment.deleteText1', {filename: attachmentToDelete.file.name}) }}<br/>
<strong class="has-text-white">{{ $t('misc.cannotBeUndone') }}</strong>
</p>
</template>
@ -159,10 +153,7 @@
:enabled="attachmentImageBlobUrl !== null"
@close="attachmentImageBlobUrl = null"
>
<img
:src="attachmentImageBlobUrl"
alt=""
>
<img :src="attachmentImageBlobUrl" alt=""/>
</modal>
</div>
</template>
@ -172,7 +163,6 @@ import {ref, shallowReactive, computed} from 'vue'
import {useDropZone} from '@vueuse/core'
import User from '@/components/misc/user.vue'
import ProgressBar from '@/components/misc/ProgressBar.vue'
import BaseButton from '@/components/base/BaseButton.vue'
import AttachmentService from '@/services/attachment'
@ -189,6 +179,9 @@ import {error, success} from '@/message'
import {useTaskStore} from '@/stores/tasks'
import {useI18n} from 'vue-i18n'
const taskStore = useTaskStore()
const {t} = useI18n({useScope: 'global'})
const {
task,
editEnabled = true,
@ -196,10 +189,9 @@ const {
task: ITask,
editEnabled: boolean,
}>()
// FIXME: this should go through the store
const emit = defineEmits(['taskChanged'])
const taskStore = useTaskStore()
const {t} = useI18n({useScope: 'global'})
const emit = defineEmits(['task-changed'])
const attachmentService = shallowReactive(new AttachmentService())
@ -275,7 +267,7 @@ function copyUrl(attachment: IAttachment) {
async function setCoverImage(attachment: IAttachment | null) {
const updatedTask = await taskStore.setCoverImage(task, attachment)
emit('taskChanged', updatedTask)
emit('task-changed', updatedTask)
success({message: t('task.attachment.successfullyChangedCoverImage')})
}
</script>

View File

@ -1,29 +1,9 @@
<template>
<span
v-if="checklist.total > 0"
class="checklist-summary"
>
<svg
width="12"
height="12"
>
<circle
stroke-width="2"
fill="transparent"
cx="50%"
cy="50%"
r="5"
/>
<circle
stroke-width="2"
stroke-dasharray="31"
:stroke-dashoffset="checklistCircleDone"
stroke-linecap="round"
fill="transparent"
cx="50%"
cy="50%"
r="5"
/>
<span v-if="checklist.total > 0" class="checklist-summary">
<svg width="12" height="12">
<circle stroke-width="2" fill="transparent" cx="50%" cy="50%" r="5"></circle>
<circle stroke-width="2" stroke-dasharray="31" :stroke-dashoffset="checklistCircleDone"
stroke-linecap="round" fill="transparent" cx="50%" cy="50%" r="5"></circle>
</svg>
<span>{{ label }}</span>
</span>

View File

@ -1,30 +1,20 @@
<template>
<div
v-if="enabled"
class="content details"
>
<h3
v-if="canWrite || comments.length > 0"
:class="{'d-print-none': comments.length === 0}"
>
<div class="content details" v-if="enabled">
<h3 v-if="canWrite || comments.length > 0" :class="{'d-print-none': comments.length === 0}">
<span class="icon is-grey">
<icon :icon="['far', 'comments']" />
<icon :icon="['far', 'comments']"/>
</span>
{{ $t('task.comment.title') }}
</h3>
<div class="comments">
<span
v-if="taskCommentService.loading && saving === null && !creating"
class="is-inline-flex is-align-items-center"
v-if="taskCommentService.loading && saving === null && !creating"
>
<span class="loader is-inline-block mr-2" />
<span class="loader is-inline-block mr-2"></span>
{{ $t('task.comment.loading') }}
</span>
<div
v-for="c in comments"
:key="c.id"
class="media comment"
>
<div :key="c.id" class="media comment" v-for="c in comments">
<figure class="media-left is-hidden-mobile">
<img
:src="getAvatarUrl(c.author, 48)"
@ -32,7 +22,7 @@
class="image is-avatar"
height="48"
width="48"
>
/>
</figure>
<div class="media-content">
<div class="comment-info">
@ -42,12 +32,9 @@
class="image is-avatar d-print-none"
height="20"
width="20"
>
/>
<strong>{{ getDisplayName(c.author) }}</strong>&nbsp;
<span
v-tooltip="formatDateLong(c.created)"
class="has-text-grey"
>
<span v-tooltip="formatDateLong(c.created)" class="has-text-grey">
{{ formatDateSince(c.created) }}
</span>
<span
@ -58,35 +45,32 @@
</span>
<CustomTransition name="fade">
<span
class="is-inline-flex"
v-if="
taskCommentService.loading &&
saving === c.id
saving === c.id
"
class="is-inline-flex"
>
<span class="loader is-inline-block mr-2" />
<span class="loader is-inline-block mr-2"></span>
{{ $t('misc.saving') }}
</span>
<span
class="has-text-success"
v-else-if="
!taskCommentService.loading &&
saved === c.id
saved === c.id
"
class="has-text-success"
>
{{ $t('misc.saved') }}
</span>
</CustomTransition>
</div>
<Editor
v-model="c.comment"
<editor
:is-edit-enabled="canWrite && c.author.id === currentUserId"
:upload-callback="attachmentUpload"
:upload-enabled="true"
:bottom-actions="actions[c.id]"
:show-save="true"
initial-mode="preview"
@update:modelValue="
v-model="c.comment"
@update:model-value="
() => {
toggleEdit(c)
editCommentWithDelay()
@ -96,13 +80,13 @@
toggleEdit(c)
editComment()
}"
:bottom-actions="actions[c.id]"
:show-save="true"
initial-mode="preview"
/>
</div>
</div>
<div
v-if="canWrite"
class="media comment d-print-none"
>
<div class="media comment d-print-none" v-if="canWrite">
<figure class="media-left is-hidden-mobile">
<img
:src="userAvatar"
@ -110,7 +94,7 @@
class="image is-avatar"
height="48"
width="48"
>
/>
</figure>
<div class="media-content">
<div class="form">
@ -119,14 +103,12 @@
v-if="taskCommentService.loading && creating"
class="is-inline-flex"
>
<span class="loader is-inline-block mr-2" />
<span class="loader is-inline-block mr-2"></span>
{{ $t('task.comment.creating') }}
</span>
</CustomTransition>
<div class="field">
<Editor
v-if="editorActive"
v-model="newComment.comment"
<editor
:class="{
'is-loading':
taskCommentService.loading &&
@ -135,6 +117,8 @@
:upload-callback="attachmentUpload"
:upload-enabled="true"
:placeholder="$t('task.comment.placeholder')"
v-if="editorActive"
v-model="newComment.comment"
@save="addComment()"
/>
</div>
@ -157,13 +141,11 @@
@close="showDeleteModal = false"
@submit="() => deleteComment(commentToDelete)"
>
<template #header>
<span>{{ $t('task.comment.delete') }}</span>
</template>
<template #header><span>{{ $t('task.comment.delete') }}</span></template>
<template #text>
<p>
{{ $t('task.comment.deleteText1') }}<br>
{{ $t('task.comment.deleteText1') }}<br/>
<strong class="has-text-white">{{ $t('misc.cannotBeUndone') }}</strong>
</p>
</template>

View File

@ -1,42 +1,24 @@
<template>
<p class="created">
<time
v-tooltip="formatDateLong(task.created)"
:datetime="formatISO(task.created)"
>
<i18n-t
keypath="task.detail.created"
scope="global"
>
<time :datetime="formatISO(task.created)" v-tooltip="formatDateLong(task.created)">
<i18n-t keypath="task.detail.created" scope="global">
<span>{{ formatDateSince(task.created) }}</span>
{{ getDisplayName(task.createdBy) }}
</i18n-t>
</time>
<template v-if="+new Date(task.created) !== +new Date(task.updated)">
<br>
<br/>
<!-- Computed properties to show the actual date every time it gets updated -->
<time
v-tooltip="updatedFormatted"
:datetime="formatISO(task.updated)"
>
<i18n-t
keypath="task.detail.updated"
scope="global"
>
<time :datetime="formatISO(task.updated)" v-tooltip="updatedFormatted">
<i18n-t keypath="task.detail.updated" scope="global">
<span>{{ updatedSince }}</span>
</i18n-t>
</time>
</template>
<template v-if="task.done">
<br>
<time
v-tooltip="doneFormatted"
:datetime="formatISO(task.doneAt)"
>
<i18n-t
keypath="task.detail.doneAt"
scope="global"
>
<br/>
<time :datetime="formatISO(task.doneAt)" v-tooltip="doneFormatted">
<i18n-t keypath="task.detail.doneAt" scope="global">
<span>{{ doneSince }}</span>
</i18n-t>
</time>

View File

@ -6,33 +6,33 @@
<label class="label">{{ $t('task.deferDueDate.title') }}</label>
<div class="defer-days">
<x-button
@click.prevent.stop="() => deferDays(1)"
:shadow="false"
variant="secondary"
@click.prevent.stop="() => deferDays(1)"
>
{{ $t('task.deferDueDate.1day') }}
</x-button>
<x-button
@click.prevent.stop="() => deferDays(3)"
:shadow="false"
variant="secondary"
@click.prevent.stop="() => deferDays(3)"
>
{{ $t('task.deferDueDate.3days') }}
</x-button>
<x-button
@click.prevent.stop="() => deferDays(7)"
:shadow="false"
variant="secondary"
@click.prevent.stop="() => deferDays(7)"
>
{{ $t('task.deferDueDate.1week') }}
</x-button>
</div>
<flat-pickr
v-model="dueDate"
:class="{ disabled: taskService.loading }"
:config="flatPickerConfig"
:disabled="taskService.loading || undefined"
class="input"
v-model="dueDate"
/>
</div>
</template>

View File

@ -2,35 +2,29 @@
<div>
<h3>
<span class="icon is-grey">
<icon icon="align-left" />
<icon icon="align-left"/>
</span>
{{ $t('task.attributes.description') }}
<CustomTransition name="fade">
<span
v-if="loading && saving"
class="is-small is-inline-flex"
>
<span class="loader is-inline-block mr-2" />
<span class="is-small is-inline-flex" v-if="loading && saving">
<span class="loader is-inline-block mr-2"></span>
{{ $t('misc.saving') }}
</span>
<span
v-else-if="!loading && saved"
class="is-small has-text-success"
>
<icon icon="check" />
<span class="is-small has-text-success" v-else-if="!loading && saved">
<icon icon="check"/>
{{ $t('misc.saved') }}
</span>
</CustomTransition>
</h3>
<Editor
v-model="description"
<editor
class="tiptap__task-description"
:is-edit-enabled="canWrite"
:upload-callback="uploadCallback"
:placeholder="$t('task.description.placeholder')"
:show-save="true"
edit-shortcut="e"
@update:modelValue="saveWithDelay"
v-model="description"
@update:model-value="saveWithDelay"
@save="save"
/>
</div>

View File

@ -1,31 +1,21 @@
<template>
<Multiselect
v-model="assignees"
class="edit-assignees"
:class="{'has-assignees': assignees.length > 0}"
:loading="projectUserService.loading"
:placeholder="$t('task.assignee.placeholder')"
:multiple="true"
@search="findUser"
:search-results="foundUsers"
@select="addAssignee"
label="name"
:select-placeholder="$t('task.assignee.selectPlaceholder')"
v-model="assignees"
:autocomplete-enabled="false"
@search="findUser"
@select="addAssignee"
>
<template #items="{items}">
<AssigneeList
:assignees="items"
:remove="removeAssignee"
:disabled="disabled"
/>
<assignee-list :assignees="items" :remove="removeAssignee" :disabled="disabled"/>
</template>
<template #searchResult="{option: user}">
<User
:avatar-size="24"
:show-username="true"
:user="user"
/>
<user :avatar-size="24" :show-username="true" :user="user"/>
</template>
</Multiselect>
</template>
@ -125,9 +115,3 @@ async function findUser(query: string) {
})
}
</script>
<style lang="scss">
.edit-assignees.has-assignees.multiselect .input {
padding-left: 0;
}
</style>

View File

@ -1,44 +1,37 @@
<template>
<Multiselect
v-model="labels"
:loading="loading"
:placeholder="$t('task.label.placeholder')"
:multiple="true"
@search="findLabel"
:search-results="foundLabels"
@select="addLabel"
label="title"
:creatable="creatable"
@create="createAndAddLabel"
:create-placeholder="$t('task.label.createPlaceholder')"
v-model="labels"
:search-delay="10"
:close-after-select="false"
@search="findLabel"
@select="addLabel"
@create="createAndAddLabel"
>
<template #tag="{item: label}">
<span
:style="{'background': label.hexColor, 'color': label.textColor}"
class="tag"
>
class="tag">
<span>{{ label.title }}</span>
<BaseButton
v-cy="'taskDetail.removeLabel'"
class="delete is-small"
@click="removeLabel(label)"
/>
<BaseButton v-cy="'taskDetail.removeLabel'" @click="removeLabel(label)" class="delete is-small" />
</span>
</template>
<template #searchResult="{option}">
<span
v-if="typeof option === 'string'"
class="tag search-result"
>
class="tag search-result">
<span>{{ option }}</span>
</span>
<span
v-else
:style="{'background': option.hexColor, 'color': option.textColor}"
class="tag search-result"
>
class="tag search-result">
<span>{{ option.title }}</span>
</span>
</template>

View File

@ -1,15 +1,8 @@
<template>
<div class="heading">
<div class="flex is-align-items-center">
<BaseButton @click="copyUrl">
<h1 class="title task-id">
{{ textIdentifier }}
</h1>
</BaseButton>
<Done
class="heading__done"
:is-done="task.done"
/>
<BaseButton @click="copyUrl"><h1 class="title task-id">{{ textIdentifier }}</h1></BaseButton>
<Done class="heading__done" :is-done="task.done"/>
<ColorBubble
v-if="task.hexColor !== ''"
:color="getHexColor(task.hexColor)"
@ -19,10 +12,10 @@
<h1
class="title input"
:class="{'disabled': !canWrite}"
:contenteditable="canWrite ? true : undefined"
:spellcheck="false"
@blur="save(($event.target as HTMLInputElement).textContent as string)"
@keydown.enter.prevent.stop="($event.target as HTMLInputElement).blur()"
:contenteditable="canWrite ? true : undefined"
:spellcheck="false"
>
{{ task.title.trim() }}
</h1>
@ -31,17 +24,14 @@
v-if="loading && saving"
class="is-inline-flex is-align-items-center"
>
<span class="loader is-inline-block mr-2" />
<span class="loader is-inline-block mr-2"></span>
{{ $t('misc.saving') }}
</span>
<span
v-else-if="!loading && showSavedMessage"
class="has-text-success is-inline-flex is-align-content-center"
>
<icon
icon="check"
class="mr-2"
/>
<icon icon="check" class="mr-2"/>
{{ $t('misc.saved') }}
</span>
</CustomTransition>

View File

@ -4,10 +4,10 @@
:class="{
'is-loading': loadingInternal || loading,
'draggable': !(loadingInternal || loading),
'has-light-text': !colorIsDark(color),
'has-custom-background-color': color ?? undefined,
'has-light-text': color !== TASK_DEFAULT_COLOR && !colorIsDark(color),
'has-custom-background-color': color !== TASK_DEFAULT_COLOR ? color : undefined,
}"
:style="{'background-color': color ?? undefined}"
:style="{'background-color': color !== TASK_DEFAULT_COLOR ? color : undefined}"
@click.exact="openTaskDetail()"
@click.ctrl="() => toggleTaskDone(task)"
@click.meta="() => toggleTaskDone(task)"
@ -17,14 +17,10 @@
:src="coverImageBlobUrl"
alt=""
class="cover-image"
>
/>
<div class="p-2">
<span class="task-id">
<Done
class="kanban-card__done"
:is-done="task.done"
variant="small"
/>
<Done class="kanban-card__done" :is-done="task.done" variant="small"/>
<template v-if="task.identifier === ''">
#{{ task.index }}
</template>
@ -33,59 +29,45 @@
</template>
</span>
<span
v-if="task.dueDate > 0"
v-tooltip="formatDateLong(task.dueDate)"
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
class="due-date"
>
v-if="task.dueDate > 0"
v-tooltip="formatDateLong(task.dueDate)">
<span class="icon">
<icon :icon="['far', 'calendar-alt']" />
<icon :icon="['far', 'calendar-alt']"/>
</span>
<time :datetime="formatISO(task.dueDate)">
{{ formatDateSince(task.dueDate) }}
</time>
</span>
<h3>{{ task.title }}</h3>
<ProgressBar
<progress
class="progress is-small"
v-if="task.percentDone > 0"
class="task-progress"
:value="task.percentDone * 100"
/>
:value="task.percentDone * 100" max="100">
{{ task.percentDone * 100 }}%
</progress>
<div class="footer">
<Labels :labels="task.labels" />
<PriorityLabel
<labels :labels="task.labels"/>
<priority-label
:priority="task.priority"
:done="task.done"
class="is-inline-flex is-align-items-center"
/>
<AssigneeList
class="is-inline-flex is-align-items-center"/>
<assignee-list
v-if="task.assignees.length > 0"
:assignees="task.assignees"
:avatar-size="24"
class="mr-1"
/>
<ChecklistSummary
:task="task"
class="checklist"
/>
<span
v-if="task.attachments.length > 0"
class="icon"
>
<icon icon="paperclip" />
<checklist-summary :task="task" class="checklist"/>
<span class="icon" v-if="task.attachments.length > 0">
<icon icon="paperclip"/>
</span>
<span
v-if="!isEditorContentEmpty(task.description)"
class="icon"
>
<icon icon="align-left" />
<span v-if="!isEditorContentEmpty(task.description)" class="icon">
<icon icon="align-left"/>
</span>
<span
v-if="task.repeatAfter.amount > 0"
class="icon"
>
<icon icon="history" />
<span class="icon" v-if="task.repeatAfter.amount > 0">
<icon icon="history"/>
</span>
</div>
</div>
@ -97,12 +79,11 @@ import {ref, computed, watch} from 'vue'
import {useRouter} from 'vue-router'
import PriorityLabel from '@/components/tasks/partials/priorityLabel.vue'
import ProgressBar from '@/components/misc/ProgressBar.vue'
import Done from '@/components/misc/Done.vue'
import Labels from '@/components/tasks/partials/labels.vue'
import ChecklistSummary from './checklist-summary.vue'
import {getHexColor} from '@/models/task'
import {TASK_DEFAULT_COLOR, getHexColor} from '@/models/task'
import type {ITask} from '@/modelTypes/ITask'
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
import AttachmentService from '@/services/attachment'
@ -115,6 +96,10 @@ import {useAuthStore} from '@/stores/auth'
import {playPopSound} from '@/helpers/playPop'
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
const router = useRouter()
const loadingInternal = ref(false)
const {
task,
loading = false,
@ -123,10 +108,6 @@ const {
loading: boolean,
}>()
const router = useRouter()
const loadingInternal = ref(false)
const color = computed(() => getHexColor(task.hexColor))
async function toggleTaskDone(task: ITask) {
@ -206,6 +187,11 @@ $task-background: var(--white);
word-break: break-word;
}
.progress {
margin: 8px 0 0 0;
width: 100%;
height: 0.5rem;
}
.due-date {
float: right;
@ -345,10 +331,4 @@ $task-background: var(--white);
.kanban-card__done {
margin-right: .25rem;
}
.task-progress {
margin: 8px 0 0 0;
width: 100%;
height: 0.5rem;
}
</style>

View File

@ -2,8 +2,8 @@
<div class="label-wrapper">
<XLabel
v-for="label in labels"
:key="label.id"
:label="label"
:key="label.id"
/>
</div>
</template>
@ -25,9 +25,5 @@ defineProps({
<style lang="scss" scoped>
.label-wrapper {
display: inline;
:deep(.tag) {
margin-bottom: .25rem;
}
}
</style>

View File

@ -4,39 +4,17 @@
v-model.number="percentDone"
:disabled="disabled || undefined"
>
<option value="0">
0%
</option>
<option value="0.1">
10%
</option>
<option value="0.2">
20%
</option>
<option value="0.3">
30%
</option>
<option value="0.4">
40%
</option>
<option value="0.5">
50%
</option>
<option value="0.6">
60%
</option>
<option value="0.7">
70%
</option>
<option value="0.8">
80%
</option>
<option value="0.9">
90%
</option>
<option value="1">
100%
</option>
<option value="0">0%</option>
<option value="0.1">10%</option>
<option value="0.2">20%</option>
<option value="0.3">30%</option>
<option value="0.4">40%</option>
<option value="0.5">50%</option>
<option value="0.6">60%</option>
<option value="0.7">70%</option>
<option value="0.8">80%</option>
<option value="0.9">90%</option>
<option value="1">100%</option>
</select>
</div>
</template>

View File

@ -1,14 +1,10 @@
<template>
<span
v-if="!done && (showAll || priority >= priorities.HIGH)"
:class="{'not-so-high': priority === priorities.HIGH, 'high-priority': priority >= priorities.HIGH}"
class="priority-label"
>
<span
v-if="priority >= priorities.HIGH"
class="icon"
>
<icon icon="exclamation" />
v-if="!done && (showAll || priority >= priorities.HIGH)">
<span class="icon" v-if="priority >= priorities.HIGH">
<icon icon="exclamation"/>
</span>
<span>
<template v-if="priority === priorities.UNSET">{{ $t('task.priority.unset') }}</template>
@ -18,11 +14,8 @@
<template v-if="priority === priorities.URGENT">{{ $t('task.priority.urgent') }}</template>
<template v-if="priority === priorities.DO_NOW">{{ $t('task.priority.doNow') }}</template>
</span>
<span
v-if="priority === priorities.DO_NOW"
class="icon pr-0"
>
<icon icon="exclamation" />
<span class="icon pr-0" v-if="priority === priorities.DO_NOW">
<icon icon="exclamation"/>
</span>
</span>
</template>

View File

@ -2,27 +2,15 @@
<div class="select">
<select
v-model="priority"
:disabled="disabled || undefined"
@change="updateData"
:disabled="disabled || undefined"
>
<option :value="PRIORITIES.UNSET">
{{ $t('task.priority.unset') }}
</option>
<option :value="PRIORITIES.LOW">
{{ $t('task.priority.low') }}
</option>
<option :value="PRIORITIES.MEDIUM">
{{ $t('task.priority.medium') }}
</option>
<option :value="PRIORITIES.HIGH">
{{ $t('task.priority.high') }}
</option>
<option :value="PRIORITIES.URGENT">
{{ $t('task.priority.urgent') }}
</option>
<option :value="PRIORITIES.DO_NOW">
{{ $t('task.priority.doNow') }}
</option>
<option :value="PRIORITIES.UNSET">{{ $t('task.priority.unset') }}</option>
<option :value="PRIORITIES.LOW">{{ $t('task.priority.low') }}</option>
<option :value="PRIORITIES.MEDIUM">{{ $t('task.priority.medium') }}</option>
<option :value="PRIORITIES.HIGH">{{ $t('task.priority.high') }}</option>
<option :value="PRIORITIES.URGENT">{{ $t('task.priority.urgent') }}</option>
<option :value="PRIORITIES.DO_NOW">{{ $t('task.priority.doNow') }}</option>
</select>
</div>
</template>

View File

@ -6,15 +6,12 @@
label="title"
:select-placeholder="$t('project.searchSelect')"
:model-value="project"
@update:modelValue="Object.assign(project, $event)"
@update:model-value="Object.assign(project, $event)"
@select="select"
@search="findProjects"
>
<template #searchResult="{option}">
<span
v-if="projectStore.getAncestors(option).length > 1"
class="has-text-grey"
>
<span class="has-text-grey" v-if="projectStore.getAncestors(option).length > 1">
{{ projectStore.getAncestors(option).filter(p => p.id !== option.id).map(p => getProjectTitle(p)).join(' &gt; ') }} &gt;
</span>
{{ getProjectTitle(option) }}

View File

@ -1,25 +1,22 @@
<template>
<template v-if="mode !== 'disabled' && prefixes !== undefined">
<BaseButton
v-tooltip="$t('task.quickAddMagic.hint')"
@click="() => visible = true"
class="icon is-small show-helper-text"
v-tooltip="$t('task.quickAddMagic.hint')"
:aria-label="$t('task.quickAddMagic.hint')"
:class="{'is-highlighted': highlightHintIcon}"
@click="() => visible = true"
>
<icon :icon="['far', 'circle-question']" />
<icon :icon="['far', 'circle-question']"/>
</BaseButton>
<modal
:enabled="visible"
@close="() => visible = false"
transition-name="fade"
:overflow="true"
variant="hint-modal"
@close="() => visible = false"
>
<card
class="has-no-shadow"
:title="$t('task.quickAddMagic.title')"
>
<card class="has-no-shadow" :title="$t('task.quickAddMagic.title')">
<p>{{ $t('task.quickAddMagic.intro') }}</p>
<h3>{{ $t('task.attributes.labels') }}</h3>
@ -105,15 +102,15 @@ import BaseButton from '@/components/base/BaseButton.vue'
import {PREFIXES} from '@/modules/parseTaskText'
import {useAuthStore} from '@/stores/auth'
defineProps<{
highlightHintIcon?: boolean,
}>()
const authStore = useAuthStore()
const visible = ref(false)
const mode = computed(() => authStore.settings.frontendSettings.quickAddMagicMode)
defineProps<{
highlightHintIcon?: boolean,
}>()
const prefixes = computed(() => PREFIXES[mode.value])
</script>

View File

@ -2,53 +2,41 @@
<div class="task-relations">
<x-button
v-if="editEnabled && Object.keys(relatedTasks).length > 0"
id="showRelatedTasksFormButton"
v-tooltip="$t('task.relation.add')"
@click="showNewRelationForm = !showNewRelationForm"
class="is-pulled-right add-task-relation-button d-print-none"
:class="{'is-active': showNewRelationForm}"
v-tooltip="$t('task.relation.add')"
variant="secondary"
icon="plus"
:shadow="false"
@click="showNewRelationForm = !showNewRelationForm"
id="showRelatedTasksFormButton"
/>
<transition-group name="fade">
<template v-if="editEnabled && showCreate">
<label
key="label"
class="label"
>
<label class="label" key="label">
{{ $t('task.relation.new') }}
<CustomTransition name="fade">
<span
v-if="taskRelationService.loading"
class="is-inline-flex"
>
<span class="loader is-inline-block mr-2" />
<span class="is-inline-flex" v-if="taskRelationService.loading">
<span class="loader is-inline-block mr-2"></span>
{{ $t('misc.saving') }}
</span>
<span
v-else-if="!taskRelationService.loading && saved"
class="has-text-success"
>
<span class="has-text-success" v-else-if="!taskRelationService.loading && saved">
{{ $t('misc.saved') }}
</span>
</CustomTransition>
</label>
<div
key="field-search"
class="field"
>
<div class="field" key="field-search">
<Multiselect
v-model="newTaskRelation.task"
v-focus
:placeholder="$t('task.relation.searchPlaceholder')"
@search="findTasks"
:loading="taskService.loading"
:search-results="mappedFoundTasks"
label="title"
v-model="newTaskRelation.task"
:creatable="true"
:create-placeholder="$t('task.relation.createPlaceholder')"
@search="findTasks"
@create="createAndRelateTask"
v-focus
>
<template #searchResult="{option: task}">
<span
@ -57,86 +45,62 @@
:class="{'is-strikethrough': task.done}"
>
<span
v-if="task.projectId !== projectId"
class="different-project"
v-if="task.projectId !== projectId"
>
<span
v-if="task.differentProject !== null"
v-tooltip="$t('task.relation.differentProject')"
>
v-tooltip="$t('task.relation.differentProject')">
{{ task.differentProject }} >
</span>
</span>
{{ task.title }}
</span>
<span
v-else
class="search-result"
>
<span class="search-result" v-else>
{{ task }}
</span>
</template>
</Multiselect>
</div>
<div
key="field-kind"
class="field has-addons mb-4"
>
<div class="field has-addons mb-4" key="field-kind">
<div class="control is-expanded">
<div class="select is-fullwidth has-defaults">
<select v-model="newTaskRelation.kind">
<option value="unset">
{{ $t('task.relation.select') }}
</option>
<option
v-for="rk in RELATION_KINDS"
:key="`option_${rk}`"
:value="rk"
>
<option value="unset">{{ $t('task.relation.select') }}</option>
<option :key="`option_${rk}`" :value="rk" v-for="rk in RELATION_KINDS">
{{ $t(`task.relation.kinds.${rk}`, 1) }}
</option>
</select>
</div>
</div>
<div class="control">
<x-button @click="addTaskRelation()">
{{ $t('task.relation.add') }}
</x-button>
<x-button @click="addTaskRelation()">{{ $t('task.relation.add') }}</x-button>
</div>
</div>
</template>
</transition-group>
<div
v-for="rts in mappedRelatedTasks"
:key="rts.kind"
class="related-tasks"
>
<div :key="rts.kind" class="related-tasks" v-for="rts in mappedRelatedTasks">
<span class="title">{{ rts.title }}</span>
<div class="tasks">
<div
v-for="t in rts.tasks"
:key="t.id"
class="task"
>
<div :key="t.id" class="task" v-for="t in rts.tasks">
<div class="is-flex is-align-items-center">
<Fancycheckbox
v-model="t.done"
class="task-done-checkbox"
@update:modelValue="toggleTaskDone(t)"
v-model="t.done"
@update:model-value="toggleTaskDone(t)"
/>
<router-link
:to="{ name: route.name as string, params: { id: t.id } }"
:class="{ 'is-strikethrough': t.done}"
>
<span
v-if="t.projectId !== projectId"
class="different-project"
v-if="t.projectId !== projectId"
>
<span
v-if="t.differentProject !== null"
v-tooltip="$t('task.relation.differentProject')"
>
v-tooltip="$t('task.relation.differentProject')">
{{ t.differentProject }} >
</span>
</span>
@ -145,21 +109,18 @@
</div>
<BaseButton
v-if="editEnabled"
class="remove"
@click="setRelationToDelete({
relationKind: rts.kind,
otherTaskId: t.id
})"
class="remove"
>
<icon icon="trash-alt" />
<icon icon="trash-alt"/>
</BaseButton>
</div>
</div>
</div>
<p
v-if="showNoRelationsNotice && Object.keys(relatedTasks).length === 0"
class="none"
>
<p class="none" v-if="showNoRelationsNotice && Object.keys(relatedTasks).length === 0">
{{ $t('task.relation.noneYet') }}
</p>
@ -168,13 +129,11 @@
@close="relationToDelete = undefined"
@submit="removeTaskRelation()"
>
<template #header>
<span>{{ $t('task.relation.delete') }}</span>
</template>
<template #header><span>{{ $t('task.relation.delete') }}</span></template>
<template #text>
<p>
{{ $t('task.relation.deleteText1') }}<br>
{{ $t('task.relation.deleteText1') }}<br/>
<strong class="has-text-white">{{ $t('misc.cannotBeUndone') }}</strong>
</p>
</template>

View File

@ -10,15 +10,8 @@
</SimpleButton>
</template>
<template #content="{isOpen, close}">
<Card
class="reminder-options-popup"
:class="{'is-open': isOpen}"
:padding="false"
>
<div
v-if="activeForm === null"
class="options"
>
<Card class="reminder-options-popup" :class="{'is-open': isOpen}" :padding="false">
<div class="options" v-if="activeForm === null">
<SimpleButton
v-for="(p, k) in presets"
:key="k"
@ -29,16 +22,16 @@
{{ formatReminder(p) }}
</SimpleButton>
<SimpleButton
@click="showFormSwitch = 'relative'"
class="option-button"
:class="{'currently-active': typeof modelValue !== 'undefined' && modelValue?.relativeTo !== null && presets.find(p => p.relativePeriod === modelValue?.relativePeriod && modelValue?.relativeTo === p.relativeTo) === undefined}"
@click="showFormSwitch = 'relative'"
>
{{ $t('task.reminder.custom') }}
</SimpleButton>
<SimpleButton
@click="showFormSwitch = 'absolute'"
class="option-button"
:class="{'currently-active': modelValue?.relativeTo === null}"
@click="showFormSwitch = 'absolute'"
>
{{ $t('task.reminder.dateAndTime') }}
</SimpleButton>
@ -53,7 +46,7 @@
<DatepickerInline
v-if="activeForm === 'absolute'"
v-model="reminderDate"
@update:modelValue="setReminderDateAndClose(close)"
@update:modelValue="setReminderDate(close)"
/>
<x-button
@ -88,6 +81,8 @@ import TaskReminderModel from '@/models/taskReminder'
import Card from '@/components/misc/card.vue'
import SimpleButton from '@/components/input/SimpleButton.vue'
const {t} = useI18n({useScope: 'global'})
const {
modelValue,
clearAfterUpdate = false,
@ -100,8 +95,6 @@ const {
const emit = defineEmits(['update:modelValue'])
const {t} = useI18n({useScope: 'global'})
const reminder = ref<ITaskReminder>(new TaskReminderModel())
const presets = computed<TaskReminderModel[]>(() => [
@ -112,7 +105,7 @@ const presets = computed<TaskReminderModel[]>(() => [
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY * 7, relativeTo: defaultRelativeTo},
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY * 30, relativeTo: defaultRelativeTo},
])
const reminderDate = ref<Date|null>(null)
const reminderDate = ref(null)
type availableForms = null | 'relative' | 'absolute'
@ -142,17 +135,7 @@ const reminderText = computed(() => {
watch(
() => modelValue,
(newReminder) => {
if(newReminder) {
reminder.value = newReminder
if(newReminder.relativeTo === null) {
reminderDate.value = new Date(newReminder.reminder)
}
return
}
reminder.value = new TaskReminderModel()
reminder.value = newReminder || new TaskReminderModel()
},
{immediate: true},
)
@ -165,7 +148,7 @@ function updateData() {
}
}
function setReminderDateAndClose(close) {
function setReminderDate(close) {
reminder.value.reminder = reminderDate.value === null
? null
: new Date(reminderDate.value)

Some files were not shown because too many files have changed in this diff Show More