Merge pull request 'main' (#1) from vikunja/frontend:main into main
Reviewed-on: davidangel/frontend#1
This commit is contained in:
commit
70ea1f2301
36
.drone.yml
36
.drone.yml
|
@ -96,7 +96,7 @@ steps:
|
|||
- dependencies
|
||||
|
||||
- name: test-frontend
|
||||
image: cypress/browsers:node18.12.0-chrome106-ff106
|
||||
image: cypress/browsers:node18.12.0-chrome107
|
||||
pull: always
|
||||
environment:
|
||||
CYPRESS_API_URL: http://api:3456/api/v1
|
||||
|
@ -110,8 +110,7 @@ steps:
|
|||
- sed -i 's/localhost/api/g' dist/index.html
|
||||
- corepack enable && pnpm config set store-dir .cache/pnpm
|
||||
- pnpm cypress install
|
||||
- pnpm run serve:dist & npx wait-on http://localhost:4173
|
||||
- pnpm run test:frontend --browser chrome --record
|
||||
- pnpm run test:e2e-record
|
||||
depends_on:
|
||||
- build-prod
|
||||
|
||||
|
@ -150,8 +149,10 @@ steps:
|
|||
# Override the default api url used for preview
|
||||
- sed -i 's|http://localhost:3456|https://try.vikunja.io|g' dist-preview/index.html
|
||||
- apk add --no-cache perl-utils
|
||||
- shasum -a 384 -c ./scripts/deploy-preview-netlify.js.sha384
|
||||
- node ./scripts/deploy-preview-netlify.js
|
||||
# create via:
|
||||
# `shasum -a 384 ./scripts/deploy-preview-netlify.mjs > ./scripts/deploy-preview-netlify.mjs.sha384`
|
||||
- shasum -a 384 -c ./scripts/deploy-preview-netlify.mjs.sha384
|
||||
- node ./scripts/deploy-preview-netlify.mjs
|
||||
depends_on:
|
||||
- build-prod
|
||||
when:
|
||||
|
@ -204,7 +205,7 @@ steps:
|
|||
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||
commands:
|
||||
- apk add git
|
||||
- corepack enable && pnpm config set store-dir .cache/.pnpm
|
||||
- corepack enable && pnpm config set store-dir .cache/pnpm
|
||||
- pnpm install --fetch-timeout 100000 --frozen-lockfile
|
||||
- pnpm run lint
|
||||
- "echo '{\"VERSION\": \"'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'\"}' > src/version.json"
|
||||
|
@ -365,7 +366,7 @@ steps:
|
|||
- name: docker-unstable
|
||||
image: thegeeklab/drone-docker-buildx
|
||||
privileged: true
|
||||
pull: true
|
||||
pull: always
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
|
@ -387,17 +388,27 @@ steps:
|
|||
ref:
|
||||
- refs/heads/main
|
||||
|
||||
- name: generate-tags
|
||||
image: thegeeklab/docker-autotag
|
||||
environment:
|
||||
DOCKER_AUTOTAG_VERSION: ${DRONE_TAG}
|
||||
DOCKER_AUTOTAG_EXTRA_TAGS: latest
|
||||
DOCKER_AUTOTAG_OUTPUT_FILE: .tags
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
||||
- name: docker-release
|
||||
image: thegeeklab/drone-docker-buildx
|
||||
privileged: true
|
||||
pull: true
|
||||
pull: always
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/frontend
|
||||
auto_tag: true
|
||||
build_args:
|
||||
- USE_RELEASE=true
|
||||
- RELEASE_VERSION=${DRONE_TAG##v}
|
||||
|
@ -407,7 +418,7 @@ steps:
|
|||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
- linux/arm64/v8
|
||||
depends_on: [ fetch-tags ]
|
||||
depends_on: [ generate-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
@ -451,9 +462,6 @@ kind: pipeline
|
|||
type: docker
|
||||
name: update-translations
|
||||
|
||||
depends_on:
|
||||
- build
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
|
@ -513,6 +521,6 @@ steps:
|
|||
from_secret: crowdin_key
|
||||
---
|
||||
kind: signature
|
||||
hmac: 9f26b5af73e3464e9ee1b5fbcb96854ca8a7e5f8d6ee2d85fd8376aad951b446
|
||||
hmac: 971875b90c7bb1649d1b00d022d0b594ba9b68f927bf8f0dbe840190816d676b
|
||||
|
||||
...
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# Duplicate this file and remove the '.example' suffix.
|
||||
# Adjust the values as needed.
|
||||
|
||||
VITE_IS_ONLINE=true
|
||||
VITE_WORKBOX_DEBUG=false
|
||||
SENTRY_AUTH_TOKEN=YOUR_TOKEN
|
||||
SENTRY_ORG=vikunja
|
||||
SENTRY_PROJECT=frontend-oss
|
|
@ -1,6 +1,7 @@
|
|||
name: Bug Report
|
||||
description: Found something you weren't expecting? Report it here!
|
||||
labels: kind/bug
|
||||
labels:
|
||||
- kind/bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
|
|
@ -1,26 +1,31 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist*
|
||||
*.zip
|
||||
.direnv/
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
stats.html
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
stats.html
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
/dist*
|
||||
coverage
|
||||
*.zip
|
||||
.direnv/
|
||||
|
||||
# Test files
|
||||
cypress/screenshots
|
||||
cypress/videos
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
|
@ -28,9 +33,9 @@ lerna-debug.log*
|
|||
*.sw*
|
||||
!rollup.sw.js
|
||||
|
||||
# Test files
|
||||
cypress/screenshots
|
||||
cypress/videos
|
||||
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
|
||||
# histoire
|
||||
.histoire
|
372
CHANGELOG.md
372
CHANGELOG.md
|
@ -9,6 +9,378 @@ All releases can be found on https://code.vikunja.io/frontend/releases.
|
|||
|
||||
The releases aim at the api versions which is why there are missing versions.
|
||||
|
||||
## [0.20.3] - 2023-01-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* *(BaseButton)* Prop type
|
||||
* *(ci)* Make sure the i18n sync cron job actually runs
|
||||
* *(ci)* Sign drone config
|
||||
* *(ci)* Sign drone config
|
||||
* *(ci)* Tagging logic for release docker images
|
||||
* *(ci)* Sign drone config
|
||||
* *(cypress)* Use ts for updateUserSettings
|
||||
* *(cypress)* Use env for API_URL (#2925)
|
||||
* *(drone)* Use correct property value (#2920)
|
||||
* *(drone)* Pnpm cache folder path (#2932)
|
||||
* *(faker)* Remove mock types (#2921)
|
||||
* *(i18n)* Incorrect translation string
|
||||
* *(migration)* Actually pass migration oauth code from query param
|
||||
* *(quick add magic)* Make sure assignees which don't exist are not removed from task title
|
||||
* *(task)* Update task description when switching between related tasks
|
||||
* *(task)* Don't show the list color on the task when only viewing the list (#2975)
|
||||
* *(useOnline)* Only log if actually faking state (#2924)
|
||||
* Close button hover for sidebar (#2981) ([9922fcb](9922fcba65c8dc2c46c4f085813c2fbc0d0a7df6))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
* *(deps)* Update dependency vite to v4.0.2 (#2861)
|
||||
* *(deps)* Update dependency netlify-cli to v12.4.0 (#2862)
|
||||
* *(deps)* Update typescript-eslint monorepo to v5.47.0 (#2864)
|
||||
* *(deps)* Update dependency esbuild to v0.16.10 (#2865)
|
||||
* *(deps)* Update dependency sass to v1.57.1 (#2866)
|
||||
* *(deps)* Update dependency vue-tsc to v1.0.16 (#2867)
|
||||
* *(deps)* Update dependency codemirror to v5.65.11
|
||||
* *(deps)* Update dependency @vueuse/core to v9.8.0
|
||||
* *(deps)* Update dependency vitest to v0.26.1
|
||||
* *(deps)* Update dependency @vueuse/core to v9.8.1 (#2870)
|
||||
* *(deps)* Update dependency @vueuse/core to v9.8.2
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.28.0
|
||||
* *(deps)* Update dependency cypress to v12.2.0 (#2873)
|
||||
* *(deps)* Update dependency vitest to v0.26.2 (#2874)
|
||||
* *(deps)* Update dependency vite to v4.0.3 (#2876)
|
||||
* *(deps)* Update pnpm to v7.19.0 (#2875)
|
||||
* *(deps)* Update dependency rollup to v3.8.0 (#2877)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.28.1 (#2878)
|
||||
* *(deps)* Update dependency @vueuse/core to v9.9.0 (#2881)
|
||||
* *(deps)* Update dependency rollup to v3.8.1 (#2879)
|
||||
* *(deps)* Update dependency vite-svg-loader to v4 (#2882)
|
||||
* *(deps)* Update dependency vue-tsc to v1.0.17 (#2883)
|
||||
* *(deps)* Update dependency caniuse-lite to v1.0.30001441 (#2884)
|
||||
* *(deps)* Update dependency netlify-cli to v12.5.0 (#2886)
|
||||
* *(deps)* Update pnpm to v7.20.0 (#2887)
|
||||
* *(deps)* Update dependency vue-tsc to v1.0.18 (#2888)
|
||||
* *(deps)* Update dependency happy-dom to v8.1.1 (#2885)
|
||||
* *(deps)* Update dependency @types/node to v18.11.18 (#2889)
|
||||
* *(deps)* Update typescript-eslint monorepo to v5.47.1 (#2890)
|
||||
* *(deps)* Update dependency esbuild to v0.16.11
|
||||
* *(deps)* Update dependency esbuild to v0.16.12 (#2893)
|
||||
* *(deps)* Update dependency rollup to v3.9.0 (#2894)
|
||||
* *(deps)* Update dependency rollup-plugin-visualizer to v5.9.0 (#2896)
|
||||
* *(deps)* Update dependency marked to v4.2.5 (#2880)
|
||||
* *(deps)* Update pnpm to v7.21.0 (#2895)
|
||||
* *(deps)* Update dependency eslint to v8.31.0
|
||||
* *(deps)* Update dependency vue-tsc to v1.0.19
|
||||
* *(deps)* Update dependency @types/codemirror to v5.60.6
|
||||
* *(deps)* Update dependency rollup to v3.9.1
|
||||
* *(deps)* Update dependency vitest to v0.26.3
|
||||
* *(deps)* Update dependency vite-plugin-pwa to v0.14.1 (#2909)
|
||||
* *(deps)* Update dependency esbuild to v0.16.13 (#2907)
|
||||
* *(deps)* Update typescript-eslint monorepo to v5.48.0 (#2906)
|
||||
* *(deps)* Update dependency vue-tsc to v1.0.20
|
||||
* *(deps)* Update dependency cypress to v12.3.0
|
||||
* *(deps)* Update dependency @vueuse/core to v9.10.0 (#2911)
|
||||
* *(deps)* Update pnpm to v7.22.0 (#2910)
|
||||
* *(deps)* Update dependency @vue/test-utils to v2.2.7 (#2914)
|
||||
* *(deps)* Update dependency vite to v4.0.4 (#2908)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.29.0 (#2915)
|
||||
* *(deps)* Update dependency esbuild to v0.16.14
|
||||
* *(deps)* Update dependency axios to v1
|
||||
* *(deps)* Update dependency vue-tsc to v1.0.21
|
||||
* *(deps)* Update dependency vue-tsc to v1.0.22
|
||||
* *(deps)* Update dependency dompurify to v2.4.2
|
||||
* *(deps)* Update dependency dompurify to v2.4.3 (#2931)
|
||||
* *(deps)* Update dependency postcss to v8.4.21 (#2933)
|
||||
* *(deps)* Update dependency esbuild to v0.16.15 (#2934)
|
||||
* *(deps)* Update dependency vue-tsc to v1.0.24
|
||||
* *(deps)* Update pnpm to v7.23.0 (#2940)
|
||||
* *(deps)* Update dependency happy-dom to v8.1.3 (#2939)
|
||||
* *(deps)* Update dependency esbuild to v0.16.16 (#2937)
|
||||
* *(deps)* Update dependency caniuse-lite to v1.0.30001442 (#2938)
|
||||
* *(deps)* Update dependency vitest to v0.27.0 (#2941)
|
||||
* *(deps)* Update typescript-eslint monorepo to v5.48.1 (#2942)
|
||||
* *(deps)* Update pnpm to v7.24.2 (#2944)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.30.0 (#2945)
|
||||
* *(deps)* Update pnpm to v7.24.3 (#2946)
|
||||
* *(deps)* Update dependency vitest to v0.27.1 (#2947)
|
||||
* *(deps)* Update dependency esbuild to v0.16.17 (#2948)
|
||||
* *(deps)* Update dependency rollup to v3.10.0 (#2949)
|
||||
* *(deps)* Update dependency eslint-plugin-vue to v9.9.0 (#2950)
|
||||
* *(deps)* Update pnpm to v7.25.0 (#2951)
|
||||
* *(deps)* Update dependency marked to v4.2.12 (#2952)
|
||||
* *(deps)* Update dependency esbuild to v0.17.0 (#2953)
|
||||
* *(deps)* Update dependency eslint to v8.32.0 (#2954)
|
||||
* *(deps)* Update dependency vue-advanced-cropper to v2.8.8 (#2955)
|
||||
* *(deps)* Update dependency pinia to v2.0.29 (#2956)
|
||||
* *(deps)* Update dependency @kyvg/vue3-notification to v2.8.0 (#2957)
|
||||
* *(deps)* Update dependency caniuse-lite to v1.0.30001445 (#2958)
|
||||
* *(deps)* Update dependency happy-dom to v8.1.4 (#2959)
|
||||
* *(deps)* Update dependency netlify-cli to v12.7.2 (#2960)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.31.0
|
||||
* *(deps)* Update dependency esbuild to v0.17.1 (#2963)
|
||||
* *(deps)* Update typescript-eslint monorepo to v5.48.2 (#2962)
|
||||
* *(deps)* Update dependency esbuild to v0.17.2 (#2965)
|
||||
* *(deps)* Update dependency vitest to v0.27.2 (#2966)
|
||||
* *(deps)* Update dependency @vueuse/core to v9.11.0 (#2967)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.31.1 (#2973)
|
||||
* *(deps)* Update dependency axios to v1.2.3 (#2974)
|
||||
* *(deps)* Update dependency esbuild to v0.17.3 (#2976)
|
||||
* *(deps)* Update pnpm to v7.25.1 (#2977)
|
||||
* *(deps)* Update dependency @vueuse/core to v9.11.1
|
||||
* *(deps)* Update dependency rollup to v3.10.1
|
||||
* *(deps)* Update dependency vite-plugin-inject-preload to v1.2.0 (#2983)
|
||||
* *(deps)* Update dependency vitest to v0.27.3 (#2984)
|
||||
* *(deps)* Update dependency esbuild to v0.17.4 (#2985)
|
||||
* *(deps)* Update dependency caniuse-lite to v1.0.30001447 (#2986)
|
||||
* *(deps)* Update dependency happy-dom to v8.1.5 (#2987)
|
||||
* *(deps)* Update dependency netlify-cli to v12.9.1 (#2988)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.32.1 (#2991)
|
||||
* *(deps)* Update dependency vitest to v0.28.1 (#2990)
|
||||
* *(deps)* Update dependency @types/codemirror to v5.60.7 (#2993)
|
||||
* *(deps)* Update typescript-eslint monorepo to v5.49.0 (#2994)
|
||||
* *(deps)* Update dependency start-server-and-test to v1.15.3
|
||||
* *(deps)* Update dependency @fortawesome/vue-fontawesome to v3.0.3 (#3003)
|
||||
|
||||
### Features
|
||||
|
||||
* *(cypress)* Remove getSettled
|
||||
* *(cypress)* Use cy.session
|
||||
* *(i18n)* Add Norwegian translation
|
||||
* *(netlify)* Abstract createSlug helper function (#2923)
|
||||
* *(postcss)* Mock plugin types (#2930)
|
||||
* Enable ts for rollup-plugin-visualizer (#2897) ([09d1352](09d13520b060e47be18640865befde44f59332e3))
|
||||
* Remove date-fns formatISO (#2899) ([1f25386](1f25386f54f376357722e1e589d3a8bd8288a033))
|
||||
* Add-task usability improvements (#2767) ([4be53b0](4be53b098ca909194aefb464a93b6dae99f4b9ab))
|
||||
* Remove formatISO from list-view-gantt.spec (#2922) ([a29131e](a29131e7d4be2c83c3e9046549924d1f7692c95e))
|
||||
* Add histoire ([7be8e89](7be8e892e2480f17cb5de6a69d35287906151c0f))
|
||||
* Add XButton story ([ccc85b9](ccc85b9a828488dc849758f1e89f3ba3f75967d1))
|
||||
* Add card story ([35cfb2f](35cfb2f3ca42ac83a9b943fc59818c978ee95fcc))
|
||||
* Add histoire (#2724) ([a4424e0](a4424e089cdfadb4ab3b753e6fdca818bbe82dc4))
|
||||
* Add describe project better in package.json (#2971) ([14466bf](14466bf9b7b8a3fc455c0d601205abbaf8cba4f5))
|
||||
* Add .env.local.example (#2972) ([e1b35ff](e1b35ff023679a7cb8448a06e9edeb8eccc2f727))
|
||||
* Fix broken font preloading (#2980) ([4890149](489014944a1544846875910d7d5e17e3d71b7e2d))
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
* *(config)* Remove unused URL_PREFIX const (#2926)
|
||||
* *(package)* Use pnpm commands (#2919)
|
||||
* *(tests)* Fix macos cypress and align with create vite (#2898)
|
||||
* Improve migrate title (#2968) ([56fd25e](56fd25e888cae8343f64a4c14ac5a3a760bdc7be))
|
||||
* Add has content="false" to gantt charts (#2969) ([903e9a9](903e9a9904c18ced59962fc03b4c36e5ac8cd688))
|
||||
* Use es6 imports for deploy-preview-netlify (#2970) ([2a2c27a](2a2c27af9226f441ec80d9d4f560b55cd357126c))
|
||||
|
||||
### Other
|
||||
|
||||
* *(other)* [skip ci] Updated translations via Crowdin
|
||||
* *(other)* Redirect to oidc provider if configured correctly (#2805)
|
||||
|
||||
|
||||
## [0.20.2] - 2022-12-18
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* *(bug-report.yml)* List (#2845)
|
||||
* *(quick add magic)* Don't create a new label multiple times if it is used in multiple tasks
|
||||
* *(task)* Pass a list specified via quick add magic down to all subtasks created via indention
|
||||
* *(task)* Move task color bubble next to task index and done badge on mobile
|
||||
* *(tasks)* Remove a task from its bucket when it is in the first kanban bucket
|
||||
* *(tasks)* Missing space when showing parent tasks and list title
|
||||
* *(tasks)* Translation for multiple related tasks now works
|
||||
* Move createdUpdated styles to component (#2685) ([4c458a1](4c458a1ad0761920868e3863982d5175664b3e6e))
|
||||
* Move heading styles to component (#2686) ([293402b](293402b6fdfc699661c7f287ff1759a9ce5bea17))
|
||||
* Use scss for datemathHelp (#2690) ([06775cf](06775cf4c72cf81a125b91d49c8d81e8649af661))
|
||||
* Reactive const assignment (#2692) ([4c4adfd](4c4adfdf4e79eff3e101d9f0bd68bc3e5bb76495))
|
||||
* Remove vuex leftover from setModuleLoading (#2716) ([3aaacf4](3aaacf4533c761864d3081edb92c9380df43f8b1))
|
||||
* Icon offset and color ([74ad98d](74ad98de680f8b56e42886cd1e33874bd05772fa))
|
||||
* Only load buckets if listId set (#2741) ([7db79ff](7db79ff04e4ce87d62cae7f93b67570bbc5c13be))
|
||||
* Add all json files in src (#2737) ([422e731](422e731fe0d44c2e3be603b549538a05a695b95c))
|
||||
* Vite.config imports (#2843) ([318e8c8](318e8c83a68bcb2f7953553c036f677a97b01c21))
|
||||
|
||||
### Dependencies
|
||||
|
||||
* *(deps)* Update dependency rollup to v3.3.0 (#2689)
|
||||
* *(deps)* Update dependency @types/dompurify to v2.4.0 (#2688)
|
||||
* *(deps)* Update dependency @vue/test-utils to v2.2.2 (#2696)
|
||||
* *(deps)* Update dependency caniuse-lite to v1.0.30001431
|
||||
* *(deps)* Update dependency happy-dom to v7.7.0
|
||||
* *(deps)* Update dependency netlify-cli to v12.1.1 (#2699)
|
||||
* *(deps)* Update dependency postcss-preset-env to v7.8.3 (#2701)
|
||||
* *(deps)* Update dependency vitest to v0.25.2 (#2702)
|
||||
* *(deps)* Update pnpm to v7.16.0 (#2703)
|
||||
* *(deps)* Update typescript-eslint monorepo to v5.43.0
|
||||
* *(deps)* Update dependency ufo to v1
|
||||
* *(deps)* Update dependency esbuild to v0.15.14 (#2706)
|
||||
* *(deps)* Update dependency @vue/test-utils to v2.2.3 (#2707)
|
||||
* *(deps)* Update dependency vite to v3.2.4
|
||||
* *(deps)* Update dependency typescript to v4.9.3
|
||||
* *(deps)* Update dependency cypress to v11.1.0
|
||||
* *(deps)* Update font awesome to v6.2.1 (#2712)
|
||||
* *(deps)* Update pnpm to v7.16.1 (#2717)
|
||||
* *(deps)* Update dependency pinia to v2.0.24
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.20.0 (#2720)
|
||||
* *(deps)* Update dependency eslint to v8.28.0
|
||||
* *(deps)* Update dependency esbuild to v0.15.15
|
||||
* *(deps)* Update dependency netlify-cli to v12.2.4
|
||||
* *(deps)* Update dependency @vue/test-utils to v2.2.4
|
||||
* *(deps)* Update pnpm to v7.17.0
|
||||
* *(deps)* Update dependency marked to v4.2.3
|
||||
* *(deps)* Update dependency codemirror to v5.65.10
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.20.1
|
||||
* *(deps)* Update dependency pinia to v2.0.25
|
||||
* *(deps)* Update dependency rollup to v3.4.0
|
||||
* *(deps)* Update typescript-eslint monorepo to v5.44.0
|
||||
* *(deps)* Update vueuse to v9.6.0 (#2742)
|
||||
* *(deps)* Update dependency vitest to v0.25.3 (#2743)
|
||||
* *(deps)* Update dependency cypress to v11.2.0
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.21.0
|
||||
* *(deps)* Update dependency @4tw/cypress-drag-drop to v2.2.2
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.21.1 (#2747)
|
||||
* *(deps)* Update dependency pinia to v2.0.26
|
||||
* *(deps)* Update dependency @cypress/vue to v5.0.2
|
||||
* *(deps)* Update dependency highlight.js to v11.7.0 (#2752)
|
||||
* *(deps)* Update dependency eslint-plugin-vue to v9.8.0 (#2753)
|
||||
* *(deps)* Update dependency @infectoone/vue-ganttastic to v2.1.3
|
||||
* *(deps)* Update dependency rollup to v3.5.0 (#2756)
|
||||
* *(deps)* Update pnpm to v7.17.1 (#2755)
|
||||
* *(deps)* Update dependency esbuild to v0.15.16
|
||||
* *(deps)* Update dependency pinia to v2.0.27 (#2757)
|
||||
* *(deps)* Update dependency caniuse-lite to v1.0.30001434 (#2759)
|
||||
* *(deps)* Update dependency netlify-cli to v12.2.7 (#2760)
|
||||
* *(deps)* Update dependency @kyvg/vue3-notification to v2.7.0 (#2761)
|
||||
* *(deps)* Update typescript-eslint monorepo to v5.45.0 (#2762)
|
||||
* *(deps)* Update dependency ufo to v1.0.1 (#2763)
|
||||
* *(deps)* Update dependency vue-tsc to v1.0.10 (#2764)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.22.0 (#2765)
|
||||
* *(deps)* Update dependency @types/node to v18.11.10 (#2768)
|
||||
* *(deps)* Update dependency rollup to v3.5.1 (#2769)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.23.0
|
||||
* *(deps)* Update dependency @vue/test-utils to v2.2.5 (#2773)
|
||||
* *(deps)* Update dependency eslint to v8.29.0 (#2774)
|
||||
* *(deps)* Update dependency @cypress/vue to v5.0.3 (#2775)
|
||||
* *(deps)* Update dependency vue-tsc to v1.0.11 (#2777)
|
||||
* *(deps)* Update dependency @cypress/vite-dev-server to v5 (#2776)
|
||||
* *(deps)* Update pnpm to v7.18.0 (#2778)
|
||||
* *(deps)* Update dependency esbuild to v0.15.17 (#2779)
|
||||
* *(deps)* Update dependency caniuse-lite to v1.0.30001436 (#2780)
|
||||
* *(deps)* Update dependency @vue/test-utils to v2.2.6 (#2784)
|
||||
* *(deps)* Update dependency esbuild to v0.15.18 (#2783)
|
||||
* *(deps)* Update dependency netlify-cli to v12.2.8 (#2782)
|
||||
* *(deps)* Update dependency happy-dom to v7.7.2 (#2781)
|
||||
* *(deps)* Update dependency vite to v3.2.5 (#2785)
|
||||
* *(deps)* Update dependency rollup to v3.6.0 (#2786)
|
||||
* *(deps)* Update typescript-eslint monorepo to v5.45.1 (#2787)
|
||||
* *(deps)* Update dependency vitest to v0.25.4 (#2788)
|
||||
* *(deps)* Update dependency @types/node to v18.11.11 (#2789)
|
||||
* *(deps)* Update pnpm to v7.18.1 (#2790)
|
||||
* *(deps)* Update dependency dayjs to v1.11.7 (#2791)
|
||||
* *(deps)* Update dependency cypress to v12 (#2792)
|
||||
* *(deps)* Update dependency vitest to v0.25.5 (#2793)
|
||||
* *(deps)* Update dependency marked to v4.2.4 (#2796)
|
||||
* *(deps)* Update dependency esbuild to v0.16.1 (#2795)
|
||||
* *(deps)* Update dependency cypress to v12.0.1 (#2794)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.24.0 (#2797)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.24.1 (#2798)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.24.2 (#2799)
|
||||
* *(deps)* Update dependency typescript to v4.9.4 (#2800)
|
||||
* *(deps)* Update dependency rollup to v3.7.0 (#2801)
|
||||
* *(deps)* Update dependency esbuild to v0.16.2 (#2802)
|
||||
* *(deps)* Update typescript-eslint monorepo to v5.46.0 (#2803)
|
||||
* *(deps)* Update dependency vitest to v0.25.6 (#2804)
|
||||
* *(deps)* Update dependency @cypress/vite-dev-server to v5.0.1 (#2806)
|
||||
* *(deps)* Update dependency esbuild to v0.16.3 (#2809)
|
||||
* *(deps)* Update dependency sass to v1.56.2 (#2810)
|
||||
* *(deps)* Update dependency @types/marked to v4.0.8 (#2812)
|
||||
* *(deps)* Update dependency vue-tsc to v1.0.12 (#2811)
|
||||
* *(deps)* Update dependency @types/node to v18.11.12 (#2808)
|
||||
* *(deps)* Update dependency cypress to v12.0.2 (#2807)
|
||||
* *(deps)* Update dependency @vitejs/plugin-vue to v4 (#2814)
|
||||
* *(deps)* Update dependency @vitejs/plugin-legacy to v3 (#2813)
|
||||
* *(deps)* Update dependency pinia to v2.0.28 (#2815)
|
||||
* *(deps)* Update dependency @vitejs/plugin-legacy to v3.0.1 (#2818)
|
||||
* *(deps)* Update dependency @cypress/vite-dev-server to v5.0.2 (#2819)
|
||||
* *(deps)* Update dependency rollup to v3.7.1 (#2820)
|
||||
* *(deps)* Update dependency rollup to v3.7.2 (#2822)
|
||||
* *(deps)* Update dependency esbuild to v0.16.4 (#2821)
|
||||
* *(deps)* Update dependency vitest to v0.25.7 (#2824)
|
||||
* *(deps)* Update dependency @types/node to v18.11.13 (#2823)
|
||||
* *(deps)* Update dependency happy-dom to v8 (#2831)
|
||||
* *(deps)* Update dependency postcss to v8.4.20 (#2827)
|
||||
* *(deps)* Update dependency caniuse-lite to v1.0.30001439 (#2828)
|
||||
* *(deps)* Update dependency @intlify/unplugin-vue-i18n to v0.8.1 (#2826)
|
||||
* *(deps)* Update dependency netlify-cli to v12.2.10 (#2829)
|
||||
* *(deps)* Update dependency vite-plugin-pwa to v0.14.0 (#2833)
|
||||
* *(deps)* Update dependency rollup to v3.7.3 (#2825)
|
||||
* *(deps)* Update dependency vue-tsc to v1.0.13 (#2832)
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.25.0
|
||||
* *(deps)* Update dependency vite to v4 (#2816)
|
||||
* *(deps)* Update pnpm to v7.18.2 (#2834)
|
||||
* *(deps)* Update typescript-eslint monorepo to v5.46.1 (#2837)
|
||||
* *(deps)* Update dependency @4tw/cypress-drag-drop to v2.2.3 (#2836)
|
||||
* *(deps)* Update dependency @types/node to v18.11.14 (#2839)
|
||||
* *(deps)* Update dependency cypress to v12.1.0 (#2838)
|
||||
* *(deps)* Update dependency rollup to v3.7.4 (#2840)
|
||||
* *(deps)* Update dependency vitest to v0.25.8
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.26.0
|
||||
* *(deps)* Update dependency esbuild to v0.16.5 (#2846)
|
||||
* *(deps)* Update dependency @types/node to v18.11.15
|
||||
* *(deps)* Update dependency esbuild to v0.16.6 (#2848)
|
||||
* *(deps)* Update dependency esbuild to v0.16.7
|
||||
* *(deps)* Update sentry-javascript monorepo to v7.27.0 (#2850)
|
||||
* *(deps)* Update dependency @vueuse/core to v9.7.0 (#2851)
|
||||
* *(deps)* Update dependency wait-on to v7 (#2852)
|
||||
* *(deps)* Update dependency @types/node to v18.11.16 (#2853)
|
||||
* *(deps)* Update dependency eslint to v8.30.0
|
||||
* *(deps)* Update dependency rollup to v3.7.5 (#2857)
|
||||
* *(deps)* Update dependency esbuild to v0.16.8 (#2854)
|
||||
* *(deps)* Update dependency sass to v1.57.0 (#2856)
|
||||
* *(deps)* Update dependency vue-tsc to v1.0.14 (#2860)
|
||||
* *(deps)* Update dependency esbuild to v0.16.9 (#2859)
|
||||
* *(deps)* Update dependency @types/node to v18.11.17 (#2858)
|
||||
|
||||
### Features
|
||||
|
||||
* *(ci)* Use docker buildx for multiarch builds* Filters script setup ([4bad685](4bad685f39388d59fdd8ff79a1766c55f75262c2))
|
||||
* Move select filters to dedicated components ([bb58dba](bb58dba8e07d683c75637ec88a378e873711eb29))
|
||||
* Add vite build target esnext (#2674) ([163d936](163d9366d3061c40b5db7f3aad5c2cea01948403))
|
||||
* Filters script setup (#2671) ([4a550da](4a550da6a69a50126b9d4a555b6713687347c2d3))
|
||||
* Reduce multiselect selector specificity (#2678) ([9f0f0b3](9f0f0b39f8eea399b7b03003afa5893d0b8016f8))
|
||||
* Reduce contentAuth selector specifity (#2677) ([12a8f7e](12a8f7ebe9fc556a7b0bc6e2d74e81d424ccfcf8))
|
||||
* Reduce ListWrapper selector specificity (#2679) ([599c1ba](599c1ba4b5b0861d89755addf016e8f797b49dfe))
|
||||
* Reduce dropdown-item selector specificity (#2680) ([eb4c2a4](eb4c2a4b9df93ee35404cd7143cc88b3d44f9d59))
|
||||
* Reduce attachments selector specificity (#2682) ([0f1f131](0f1f131f7a2a38ee57175edfd5ed1c932225af16))
|
||||
* Reduce ready selector specificity (#2683) ([9d604f7](9d604f7a3bc057bbe27ac19e73ac59736154d9b7))
|
||||
* Use img for logo so that it's not part of the main bundle (#2684) ([02de481](02de481297502ad4b0b2eb2fa3e06366cce6d630))
|
||||
* Improve user component (#2687) ([708ef2d](708ef2d72efbdfe6261322937b0a8f76ee19b9e4))
|
||||
* Reduce TaskDetailView selector specificity ([fba402f](fba402fcd056ee397ce54f97ed4fec98845c7933))
|
||||
* Move transition in own component ([631a19f](631a19fa923dba2759603e6a8b224cb4d3e1a038))
|
||||
* Feature/load-views-async (#2672)
|
||||
* Use transition component everywhere ([8c44ed8](8c44ed83e6530f67cc923a5e6d1a26c14575884a))
|
||||
* Move transition in component (#2694) ([77ff0aa](77ff0aa256fbf388210af09d88673475386b3553))
|
||||
* Disable fullscreen for EasyMDE side-by-side mode (#2710) ([98b38af](98b38af43c3acc9822f167ebca295f5aecb4908d))
|
||||
* Only automatically redirect to provider if the url contains ?redirectToProvider=true and it's the only one ([3891d5b](3891d5b87634c890265477680fafaa04ff06cc3e))
|
||||
* Improve loadTask logic (#2715) ([8ef3092](8ef309243db4e37d306167455987572006858cad))
|
||||
* Remove edit-task from list view (#2721) ([45ec162](45ec1623d525ed31a49b6be6d609802c341fad27))
|
||||
* Move useAutoHeightTextarea to composable (#2723) ([33d4efe](33d4efecc45ef8da5360fb878b7d365d1901b56c))
|
||||
* More horizontal space on mobile (#2722) ([b42e4cc](b42e4cca59e338278261bc3ec613eefedde6fcce))
|
||||
* Change list-content style (#91) ([4b47478](4b47478440d0af1bf24c44ea614c0f62f20723f7))
|
||||
* Grid for list cards ([42e9f30](42e9f306e84120ba51d9b527c7868148730bf892))
|
||||
* Move avatar class to where it is used (#2725) ([da8df8b](da8df8b667fc57798c1de7d78c1a7f88b0419d38))
|
||||
* Undent and order navigation css ([66be0e6](66be0e6ac4bcf48124b33267224187b56ac9320a))
|
||||
* Outdent navigation logo styles ([ff9efe7](ff9efe7889256706ac86bb1face842cd2de6f935))
|
||||
* Group navigation styles further ([4fc7b9c](4fc7b9c67e2088e82760005cd530ea97cf796a4c))
|
||||
* Move link color location together ([d9984b2](d9984b28f7d01da0f9d8f0afd5b6f0edf35823c2))
|
||||
* Use fetch instead of axios for deploy preview (#2719) ([93d95b0](93d95b0821f39719c4a28c144ebb583c2eac754e))
|
||||
* Remove useRouteQuery (#2751) ([3ee0bc3](3ee0bc345d6cd65769789ec029c50e652d80e1ca))
|
||||
* Use Intl.DateTimeFormat for gantt weekdays (#2766) ([3b95824](3b95824f5834d7de50210414c56b07889db895c7))
|
||||
* Add @intlify/unplugin-vue-i18n (#2772) ([b44d11c](b44d11cfc04712b9f9ec9479ba3a77a26c453532))
|
||||
* Use vite preview for serve:dist:dev (#2842) ([f6c6f52](f6c6f52abe71674fa5f3951cc0ba61798758bd03))
|
||||
* Use variable fonts with subsetting (#2817) ([b6a89a0](b6a89a0cde3c769e38146b05c33ff4ca4e97bca2))
|
||||
|
||||
### Other
|
||||
|
||||
* *(other)* [skip ci] Updated translations via Crowdin
|
||||
|
||||
## [0.20.1] - 2022-11-11
|
||||
|
||||
### Bug Fixes
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/frontend/status.svg)](https://drone.kolaente.de/vikunja/frontend)
|
||||
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
|
||||
[![Download](https://img.shields.io/badge/download-v0.20.1-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Download](https://img.shields.io/badge/download-v0.20.3-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Translation](https://badges.crowdin.net/vikunja/localized.svg)](https://crowdin.com/project/vikunja)
|
||||
|
||||
This is the web frontend for Vikunja, written in Vue.js.
|
||||
|
|
|
@ -11,8 +11,10 @@ export default defineConfig({
|
|||
},
|
||||
projectId: '181c7x',
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:4173',
|
||||
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
||||
specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}',
|
||||
baseUrl: 'http://127.0.0.1:4173',
|
||||
experimentalRunAllSpecs: true,
|
||||
// testIsolation: false,
|
||||
},
|
||||
component: {
|
||||
devServer: {
|
||||
|
|
|
@ -36,7 +36,7 @@ to get a shell inside the cypress container.
|
|||
In that shell you can then execute the tests with
|
||||
|
||||
```shell
|
||||
pnpm run test:frontend
|
||||
pnpm run test:e2e
|
||||
```
|
||||
|
||||
### Using The Cypress Dashboard
|
||||
|
@ -44,5 +44,5 @@ pnpm run test:frontend
|
|||
To open the Cypress Dashboard and run tests from there, run
|
||||
|
||||
```shell
|
||||
pnpm run cypress:open
|
||||
pnpm run test:e2e:dev
|
||||
```
|
||||
|
|
|
@ -9,7 +9,7 @@ services:
|
|||
ports:
|
||||
- 3456:3456
|
||||
cypress:
|
||||
image: cypress/browsers:node16.14.0-chrome99-ff97
|
||||
image: cypress/browsers:node18.12.0-chrome107
|
||||
volumes:
|
||||
- ..:/project
|
||||
- $HOME/.cache:/home/node/.cache/
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import {ListFactory} from '../../factories/list'
|
||||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
import {ListFactory} from '../../factories/list'
|
||||
import {prepareLists} from './prepareLists'
|
||||
|
||||
describe('List History', () => {
|
||||
createFakeUserAndLogin()
|
||||
prepareLists()
|
||||
|
||||
it('should show a list history on the home page', () => {
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import {formatISO, format} from 'date-fns'
|
||||
|
||||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {prepareLists} from './prepareLists'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
|
||||
describe('List View Gantt', () => {
|
||||
createFakeUserAndLogin()
|
||||
prepareLists()
|
||||
|
||||
it('Hides tasks with no dates', () => {
|
||||
|
@ -33,8 +35,8 @@ describe('List View Gantt', () => {
|
|||
it('Shows tasks with dates', () => {
|
||||
const now = new Date()
|
||||
const tasks = TaskFactory.create(1, {
|
||||
start_date: formatISO(now),
|
||||
end_date: formatISO(now.setDate(now.getDate() + 4)),
|
||||
start_date: now.toISOString(),
|
||||
end_date: new Date(new Date(now).setDate(now.getDate() + 4)).toISOString(),
|
||||
})
|
||||
cy.visit('/lists/1/gantt')
|
||||
|
||||
|
@ -60,13 +62,12 @@ describe('List View Gantt', () => {
|
|||
})
|
||||
|
||||
it('Drags a task around', () => {
|
||||
cy.intercept('**/api/v1/tasks/*')
|
||||
.as('taskUpdate')
|
||||
cy.intercept(Cypress.env('API_URL') + '/tasks/*').as('taskUpdate')
|
||||
|
||||
const now = new Date()
|
||||
TaskFactory.create(1, {
|
||||
start_date: formatISO(now),
|
||||
end_date: formatISO(now.setDate(now.getDate() + 4)),
|
||||
start_date: now.toISOString(),
|
||||
end_date: new Date(new Date(now).setDate(now.getDate() + 4)).toISOString(),
|
||||
})
|
||||
cy.visit('/lists/1/gantt')
|
||||
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import {BucketFactory} from '../../factories/bucket'
|
||||
import {ListFactory} from '../../factories/list'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {prepareLists} from './prepareLists'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
|
||||
describe('List View Kanban', () => {
|
||||
let buckets
|
||||
createFakeUserAndLogin()
|
||||
prepareLists()
|
||||
|
||||
|
||||
let buckets
|
||||
beforeEach(() => {
|
||||
buckets = BucketFactory.create(2)
|
||||
})
|
||||
|
@ -38,7 +39,7 @@ describe('List View Kanban', () => {
|
|||
})
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket')
|
||||
cy.get('.kanban .bucket')
|
||||
.contains(buckets[0].title)
|
||||
.get('.bucket-footer .button')
|
||||
.contains('Add another task')
|
||||
|
@ -70,7 +71,7 @@ describe('List View Kanban', () => {
|
|||
it('Can set a bucket limit', () => {
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
|
||||
.first()
|
||||
.click()
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
|
||||
|
@ -91,7 +92,7 @@ describe('List View Kanban', () => {
|
|||
it('Can rename a bucket', () => {
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .bucket-header .title')
|
||||
cy.get('.kanban .bucket .bucket-header .title')
|
||||
.first()
|
||||
.type('{selectall}New Bucket Title{enter}')
|
||||
cy.get('.kanban .bucket .bucket-header .title')
|
||||
|
@ -102,7 +103,7 @@ describe('List View Kanban', () => {
|
|||
it('Can delete a bucket', () => {
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
|
||||
.first()
|
||||
.click()
|
||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
|
||||
|
@ -129,7 +130,7 @@ describe('List View Kanban', () => {
|
|||
})
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .tasks .task')
|
||||
cy.get('.kanban .bucket .tasks .task')
|
||||
.contains(tasks[0].title)
|
||||
.first()
|
||||
.drag('.kanban .bucket:nth-child(2) .tasks')
|
||||
|
@ -148,7 +149,7 @@ describe('List View Kanban', () => {
|
|||
})
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .tasks .task')
|
||||
cy.get('.kanban .bucket .tasks .task')
|
||||
.contains(tasks[0].title)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
@ -170,7 +171,7 @@ describe('List View Kanban', () => {
|
|||
const task = tasks[0]
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .tasks .task')
|
||||
cy.get('.kanban .bucket .tasks .task')
|
||||
.contains(task.title)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
@ -217,7 +218,7 @@ describe('List View Kanban', () => {
|
|||
const task = tasks[0]
|
||||
cy.visit('/lists/1/kanban')
|
||||
|
||||
cy.getSettled('.kanban .bucket .tasks .task')
|
||||
cy.get('.kanban .bucket .tasks .task')
|
||||
.contains(task.title)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
@ -234,7 +235,7 @@ describe('List View Kanban', () => {
|
|||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
|
||||
cy.getSettled('.kanban .bucket .tasks')
|
||||
cy.get('.kanban .bucket .tasks')
|
||||
.should('not.contain', task.title)
|
||||
})
|
||||
})
|
|
@ -1,12 +1,13 @@
|
|||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import {UserListFactory} from '../../factories/users_list'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {UserFactory} from '../../factories/user'
|
||||
import {ListFactory} from '../../factories/list'
|
||||
import {prepareLists} from './prepareLists'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
|
||||
describe('List View List', () => {
|
||||
createFakeUserAndLogin()
|
||||
prepareLists()
|
||||
|
||||
it('Should be an empty list', () => {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
|
||||
describe('List View Table', () => {
|
||||
createFakeUserAndLogin()
|
||||
|
||||
it('Should show a table with tasks', () => {
|
||||
const tasks = TaskFactory.create(1)
|
||||
cy.visit('/lists/1/table')
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {prepareLists} from './prepareLists'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
|
||||
describe('Lists', () => {
|
||||
createFakeUserAndLogin()
|
||||
|
||||
let lists
|
||||
prepareLists((newLists) => (lists = newLists))
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import {UserFactory} from '../../factories/user'
|
||||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
import {ListFactory} from '../../factories/list'
|
||||
import {NamespaceFactory} from '../../factories/namespace'
|
||||
|
||||
describe('Namepaces', () => {
|
||||
createFakeUserAndLogin()
|
||||
|
||||
let namespaces
|
||||
|
||||
beforeEach(() => {
|
||||
UserFactory.create(1)
|
||||
namespaces = NamespaceFactory.create(1)
|
||||
ListFactory.create(1)
|
||||
})
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import {ListFactory} from '../../factories/list'
|
||||
import {UserFactory} from '../../factories/user'
|
||||
import {NamespaceFactory} from '../../factories/namespace'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
|
||||
export function createLists() {
|
||||
UserFactory.create(1)
|
||||
NamespaceFactory.create(1)
|
||||
const lists = ListFactory.create(1, {
|
||||
title: 'First List'
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {ListFactory} from '../../factories/list'
|
||||
import {NamespaceFactory} from '../../factories/namespace'
|
||||
import {UserListFactory} from '../../factories/users_list'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
|
||||
describe('Editor', () => {
|
||||
createFakeUserAndLogin()
|
||||
|
||||
beforeEach(() => {
|
||||
NamespaceFactory.create(1)
|
||||
const lists = ListFactory.create(1)
|
||||
ListFactory.create(1)
|
||||
TaskFactory.truncate()
|
||||
UserListFactory.truncate()
|
||||
})
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import '../../support/authenticateUser'
|
||||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
describe('The Menu', () => {
|
||||
createFakeUserAndLogin()
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
it('Is visible by default on desktop', () => {
|
||||
cy.get('.namespace-container')
|
||||
.should('have.class', 'is-active')
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import {TeamFactory} from '../../factories/team'
|
||||
import {TeamMemberFactory} from '../../factories/team_member'
|
||||
import {UserFactory} from '../../factories/user'
|
||||
import '../../support/authenticateUser'
|
||||
|
||||
describe('Team', () => {
|
||||
createFakeUserAndLogin()
|
||||
|
||||
it('Creates a new team', () => {
|
||||
TeamFactory.truncate()
|
||||
cy.visit('/teams')
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import {ListFactory} from '../../factories/list'
|
||||
import {seed} from '../../support/seed'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {formatISO} from 'date-fns'
|
||||
import {UserFactory} from '../../factories/user'
|
||||
import {NamespaceFactory} from '../../factories/namespace'
|
||||
import {BucketFactory} from '../../factories/bucket'
|
||||
import {updateUserSettings} from '../../support/updateUserSettings'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
|
||||
function seedTasks(numberOfTasks = 100, startDueDate = new Date()) {
|
||||
UserFactory.create(1)
|
||||
function seedTasks(numberOfTasks = 50, startDueDate = new Date()) {
|
||||
NamespaceFactory.create(1)
|
||||
const list = ListFactory.create()[0]
|
||||
BucketFactory.create(1, {
|
||||
|
@ -20,7 +17,7 @@ function seedTasks(numberOfTasks = 100, startDueDate = new Date()) {
|
|||
let dueDate = startDueDate
|
||||
for (let i = 0; i < numberOfTasks; i++) {
|
||||
const now = new Date()
|
||||
dueDate = (new Date(dueDate.valueOf())).setDate((new Date(dueDate.valueOf())).getDate() + 2)
|
||||
dueDate = new Date(new Date(dueDate).setDate(dueDate.getDate() + 2))
|
||||
tasks.push({
|
||||
id: i + 1,
|
||||
list_id: list.id,
|
||||
|
@ -28,9 +25,9 @@ function seedTasks(numberOfTasks = 100, startDueDate = new Date()) {
|
|||
created_by_id: 1,
|
||||
title: 'Test Task ' + i,
|
||||
index: i + 1,
|
||||
due_date: formatISO(dueDate),
|
||||
created: formatISO(now),
|
||||
updated: formatISO(now),
|
||||
due_date: dueDate.toISOString(),
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString(),
|
||||
})
|
||||
}
|
||||
seed(TaskFactory.table, tasks)
|
||||
|
@ -38,8 +35,11 @@ function seedTasks(numberOfTasks = 100, startDueDate = new Date()) {
|
|||
}
|
||||
|
||||
describe('Home Page Task Overview', () => {
|
||||
createFakeUserAndLogin()
|
||||
|
||||
it('Should show tasks with a near due date first on the home page overview', () => {
|
||||
const {tasks} = seedTasks()
|
||||
const taskCount = 50
|
||||
const {tasks} = seedTasks(taskCount)
|
||||
|
||||
cy.visit('/')
|
||||
cy.get('[data-cy="showTasks"] .card .task')
|
||||
|
@ -49,8 +49,10 @@ describe('Home Page Task Overview', () => {
|
|||
})
|
||||
|
||||
it('Should show overdue tasks first, then show other tasks', () => {
|
||||
const oldDate = (new Date()).setDate((new Date()).getDate() - 14)
|
||||
const {tasks} = seedTasks(100, oldDate)
|
||||
const now = new Date()
|
||||
const oldDate = new Date(new Date(now).setDate(now.getDate() - 14))
|
||||
const taskCount = 50
|
||||
const {tasks} = seedTasks(taskCount, oldDate)
|
||||
|
||||
cy.visit('/')
|
||||
cy.get('[data-cy="showTasks"] .card .task')
|
||||
|
@ -68,7 +70,7 @@ describe('Home Page Task Overview', () => {
|
|||
TaskFactory.create(1, {
|
||||
id: 999,
|
||||
title: newTaskTitle,
|
||||
due_date: formatISO(new Date()),
|
||||
due_date: new Date().toISOString(),
|
||||
}, false)
|
||||
|
||||
cy.visit(`/lists/${tasks[0].list_id}/list`)
|
||||
|
@ -83,7 +85,7 @@ describe('Home Page Task Overview', () => {
|
|||
|
||||
it('Should not show a new task without a date at the bottom when there are > 50 tasks', () => {
|
||||
// We're not using the api here to create the task in order to verify the flow
|
||||
const {tasks} = seedTasks()
|
||||
const {tasks} = seedTasks(100)
|
||||
const newTaskTitle = 'New Task'
|
||||
|
||||
cy.visit('/')
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {formatISO} from 'date-fns'
|
||||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {ListFactory} from '../../factories/list'
|
||||
|
@ -11,7 +11,6 @@ import {LabelFactory} from '../../factories/labels'
|
|||
import {LabelTaskFactory} from '../../factories/label_task'
|
||||
import {BucketFactory} from '../../factories/bucket'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
import {TaskAttachmentFactory} from '../../factories/task_attachments'
|
||||
|
||||
function addLabelToTaskAndVerify(labelTitle: string) {
|
||||
|
@ -46,12 +45,14 @@ function uploadAttachmentAndVerify(taskId: number) {
|
|||
}
|
||||
|
||||
describe('Task', () => {
|
||||
createFakeUserAndLogin()
|
||||
|
||||
let namespaces
|
||||
let lists
|
||||
let buckets
|
||||
|
||||
beforeEach(() => {
|
||||
UserFactory.create(1)
|
||||
// UserFactory.create(1)
|
||||
namespaces = NamespaceFactory.create(1)
|
||||
lists = ListFactory.create(1)
|
||||
buckets = BucketFactory.create(1, {
|
||||
|
@ -145,7 +146,7 @@ describe('Task', () => {
|
|||
id: 1,
|
||||
index: 1,
|
||||
done: true,
|
||||
done_at: formatISO(new Date())
|
||||
done_at: new Date().toISOString()
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
|
@ -421,10 +422,10 @@ describe('Task', () => {
|
|||
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.getSettled('.task-view .details.labels-list .multiselect .input-wrapper')
|
||||
cy.get('.task-view .details.labels-list .multiselect .input-wrapper')
|
||||
.should('be.visible')
|
||||
.should('contain', labels[0].title)
|
||||
cy.getSettled('.task-view .details.labels-list .multiselect .input-wrapper')
|
||||
cy.get('.task-view .details.labels-list .multiselect .input-wrapper')
|
||||
.children()
|
||||
.first()
|
||||
.get('[data-cy="taskDetail.removeLabel"]')
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||
"include": ["./**/*", "../support/**/*", "../factories/**/*"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"isolatedModules": false,
|
||||
"target": "ES2015",
|
||||
"lib": ["ESNext", "dom"],
|
||||
"types": ["cypress"]
|
||||
}
|
||||
}
|
|
@ -11,16 +11,11 @@ const testAndAssertFailed = fixture => {
|
|||
cy.get('div.message.danger').contains('Wrong username or password.')
|
||||
}
|
||||
|
||||
const username = 'test'
|
||||
|
||||
context('Login', () => {
|
||||
beforeEach(() => {
|
||||
UserFactory.create(1, {
|
||||
username: 'test',
|
||||
})
|
||||
cy.visit('/', {
|
||||
onBeforeLoad(win) {
|
||||
win.localStorage.removeItem('token')
|
||||
},
|
||||
})
|
||||
UserFactory.create(1, {username})
|
||||
})
|
||||
|
||||
it('Should log in with the right credentials', () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import '../../support/authenticateUser'
|
||||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
import {createLists} from '../list/prepareLists'
|
||||
|
||||
function logout() {
|
||||
|
@ -10,6 +10,8 @@ function logout() {
|
|||
}
|
||||
|
||||
describe('Log out', () => {
|
||||
createFakeUserAndLogin()
|
||||
|
||||
it('Logs the user out', () => {
|
||||
cy.visit('/')
|
||||
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import {UserFactory} from '../../factories/user'
|
||||
|
||||
import '../../support/authenticateUser'
|
||||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
describe('User Settings', () => {
|
||||
beforeEach(() => {
|
||||
UserFactory.create(1)
|
||||
})
|
||||
createFakeUserAndLogin()
|
||||
|
||||
it('Changes the user avatar', () => {
|
||||
cy.intercept(`${Cypress.env('API_URL')}/user/settings/avatar/upload`).as('uploadAvatar')
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import {faker} from '@faker-js/faker'
|
||||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
export class BucketFactory extends Factory {
|
||||
static table = 'buckets'
|
||||
|
@ -13,8 +12,8 @@ export class BucketFactory extends Factory {
|
|||
title: faker.lorem.words(3),
|
||||
list_id: 1,
|
||||
created_by_id: 1,
|
||||
created: formatISO(now),
|
||||
updated: formatISO(now)
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
export class LabelTaskFactory extends Factory {
|
||||
static table = 'label_tasks'
|
||||
|
@ -11,7 +10,7 @@ export class LabelTaskFactory extends Factory {
|
|||
id: '{increment}',
|
||||
task_id: 1,
|
||||
label_id: 1,
|
||||
created: formatISO(now),
|
||||
created: now.toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import {faker} from '@faker-js/faker'
|
||||
|
||||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
export class LabelFactory extends Factory {
|
||||
static table = 'labels'
|
||||
|
@ -15,8 +14,8 @@ export class LabelFactory extends Factory {
|
|||
description: faker.lorem.text(10),
|
||||
hex_color: (Math.random()*0xFFFFFF<<0).toString(16), // random 6-digit hex number
|
||||
created_by_id: 1,
|
||||
created: formatISO(now),
|
||||
updated: formatISO(now),
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from "date-fns"
|
||||
import {faker} from '@faker-js/faker'
|
||||
|
||||
export class LinkShareFactory extends Factory {
|
||||
|
@ -15,8 +14,8 @@ export class LinkShareFactory extends Factory {
|
|||
right: 0,
|
||||
sharing_type: 0,
|
||||
shared_by_id: 1,
|
||||
created: formatISO(now),
|
||||
updated: formatISO(now)
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from "date-fns"
|
||||
import {faker} from '@faker-js/faker'
|
||||
|
||||
export class ListFactory extends Factory {
|
||||
|
@ -13,8 +12,8 @@ export class ListFactory extends Factory {
|
|||
title: faker.lorem.words(3),
|
||||
owner_id: 1,
|
||||
namespace_id: 1,
|
||||
created: formatISO(now),
|
||||
updated: formatISO(now)
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
import {faker} from '@faker-js/faker'
|
||||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
export class NamespaceFactory extends Factory {
|
||||
static table = 'namespaces'
|
||||
|
@ -12,8 +11,8 @@ export class NamespaceFactory extends Factory {
|
|||
id: '{increment}',
|
||||
title: faker.lorem.words(3),
|
||||
owner_id: 1,
|
||||
created: formatISO(now),
|
||||
updated: formatISO(now)
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import {faker} from '@faker-js/faker'
|
||||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
export class TaskFactory extends Factory {
|
||||
static table = 'tasks'
|
||||
|
@ -16,8 +15,8 @@ export class TaskFactory extends Factory {
|
|||
created_by_id: 1,
|
||||
index: '{increment}',
|
||||
position: '{increment}',
|
||||
created: formatISO(now),
|
||||
updated: formatISO(now)
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
export class TaskAssigneeFactory extends Factory {
|
||||
static table = 'task_assignees'
|
||||
|
@ -11,7 +10,7 @@ export class TaskAssigneeFactory extends Factory {
|
|||
id: '{increment}',
|
||||
task_id: 1,
|
||||
user_id: 1,
|
||||
created: formatISO(now),
|
||||
created: now.toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
export class TaskAttachmentFactory extends Factory {
|
||||
static table = 'task_attachments'
|
||||
|
@ -11,7 +10,7 @@ export class TaskAttachmentFactory extends Factory {
|
|||
id: '{increment}',
|
||||
task_id: 1,
|
||||
file_id: 1,
|
||||
created: formatISO(now),
|
||||
created: now.toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import {faker} from '@faker-js/faker'
|
||||
|
||||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from "date-fns"
|
||||
|
||||
export class TaskCommentFactory extends Factory {
|
||||
static table = 'task_comments'
|
||||
|
@ -14,8 +13,8 @@ export class TaskCommentFactory extends Factory {
|
|||
comment: faker.lorem.text(3),
|
||||
author_id: 1,
|
||||
task_id: 1,
|
||||
created: formatISO(now),
|
||||
updated: formatISO(now)
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import {faker} from '@faker-js/faker'
|
||||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
export class TeamFactory extends Factory {
|
||||
static table = 'teams'
|
||||
|
@ -11,8 +10,8 @@ export class TeamFactory extends Factory {
|
|||
return {
|
||||
name: faker.lorem.words(3),
|
||||
created_by_id: 1,
|
||||
created: formatISO(now),
|
||||
updated: formatISO(now)
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
export class TeamMemberFactory extends Factory {
|
||||
static table = 'team_members'
|
||||
|
@ -9,7 +8,7 @@ export class TeamMemberFactory extends Factory {
|
|||
team_id: 1,
|
||||
user_id: 1,
|
||||
admin: false,
|
||||
created: formatISO(new Date()),
|
||||
created: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import {faker} from '@faker-js/faker'
|
||||
|
||||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from "date-fns"
|
||||
|
||||
export class UserFactory extends Factory {
|
||||
static table = 'users'
|
||||
|
@ -15,8 +14,8 @@ export class UserFactory extends Factory {
|
|||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.', // 1234
|
||||
status: 0,
|
||||
issuer: 'local',
|
||||
created: formatISO(now),
|
||||
updated: formatISO(now)
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import {Factory} from '../support/factory'
|
||||
import {formatISO} from "date-fns"
|
||||
|
||||
export class UserListFactory extends Factory {
|
||||
static table = 'users_lists'
|
||||
|
@ -12,8 +11,8 @@ export class UserListFactory extends Factory {
|
|||
list_id: 1,
|
||||
user_id: 1,
|
||||
right: 0,
|
||||
created: formatISO(now),
|
||||
updated: formatISO(now)
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,26 +4,32 @@
|
|||
|
||||
import {UserFactory} from '../factories/user'
|
||||
|
||||
let token
|
||||
|
||||
before(() => {
|
||||
const users = UserFactory.create(1)
|
||||
|
||||
cy.request('POST', `${Cypress.env('API_URL')}/login`, {
|
||||
username: users[0].username,
|
||||
password: '1234',
|
||||
})
|
||||
.its('body')
|
||||
.then(r => {
|
||||
token = r.token
|
||||
export function login(user, cacheAcrossSpecs = false) {
|
||||
if (!user) {
|
||||
throw new Error('Needs user')
|
||||
}
|
||||
// Caching session when logging in via page visit
|
||||
cy.session(`user__${user.username}`, () => {
|
||||
cy.request('POST', `${Cypress.env('API_URL')}/login`, {
|
||||
username: user.username,
|
||||
password: '1234',
|
||||
}).then(({ body }) => {
|
||||
window.localStorage.setItem('token', body.token)
|
||||
})
|
||||
})
|
||||
}, {
|
||||
cacheAcrossSpecs,
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
cy.log(`Using token ${token} to make authenticated requests`)
|
||||
cy.visit('/', {
|
||||
onBeforeLoad(win) {
|
||||
win.localStorage.setItem('token', token)
|
||||
},
|
||||
export function createFakeUserAndLogin() {
|
||||
let user
|
||||
before(() => {
|
||||
user = UserFactory.create(1)[0]
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
login(user, true)
|
||||
})
|
||||
|
||||
return user
|
||||
}
|
|
@ -34,38 +34,4 @@
|
|||
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Recursively gets an element, returning only after it's determined to be attached to the DOM for good.
|
||||
*
|
||||
* Source: https://github.com/cypress-io/cypress/issues/7306#issuecomment-850621378
|
||||
*/
|
||||
Cypress.Commands.add('getSettled', (selector, opts = {}) => {
|
||||
const retries = opts.retries || 3
|
||||
const delay = opts.delay || 100
|
||||
|
||||
const isAttached = (resolve, count = 0) => {
|
||||
const el = Cypress.$(selector)
|
||||
|
||||
// is element attached to the DOM?
|
||||
count = Cypress.dom.isAttached(el) ? count + 1 : 0
|
||||
|
||||
// hit our base case, return the element
|
||||
if (count >= retries) {
|
||||
return resolve(el)
|
||||
}
|
||||
|
||||
// retry after a bit of a delay
|
||||
setTimeout(() => isAttached(resolve, count), delay)
|
||||
}
|
||||
|
||||
// wrap, so we can chain cypress commands off the result
|
||||
return cy.wrap(null).then(() => {
|
||||
return new Cypress.Promise((resolve) => {
|
||||
return isAttached(resolve, 0)
|
||||
}).then((el) => {
|
||||
return cy.wrap(el)
|
||||
})
|
||||
})
|
||||
})
|
||||
// }
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||
"include": ["./integration/**/*", "./support/**/*"],
|
||||
"compilerOptions": {
|
||||
"isolatedModules": false,
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
declare module 'postcss-easings' {
|
||||
import postcssEasings from 'postcss-easings'
|
||||
export default postcssEasings
|
||||
}
|
||||
|
||||
declare module 'postcss-easing-gradients' {
|
||||
import postcssEasingGradients from 'postcss-easing-gradients'
|
||||
export default postcssEasingGradients
|
||||
}
|
|
@ -1,3 +1,12 @@
|
|||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-svg-loader" />
|
||||
/// <reference types="cypress" />
|
||||
/// <reference types="cypress" />
|
||||
/// <reference types="@histoire/plugin-vue/components" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_IS_ONLINE: boolean
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import {defineConfig, defaultColors} from 'histoire'
|
||||
import {HstVue} from '@histoire/plugin-vue'
|
||||
import {HstScreenshot} from '@histoire/plugin-screenshot'
|
||||
|
||||
export default defineConfig({
|
||||
setupFile: './src/histoire.setup.ts',
|
||||
storyIgnored: [
|
||||
'**/node_modules/**',
|
||||
'**/dist/**',
|
||||
// see https://kolaente.dev/vikunja/frontend/pulls/2724#issuecomment-42012
|
||||
'**/.direnv/**',
|
||||
],
|
||||
plugins: [
|
||||
HstVue(),
|
||||
HstScreenshot({
|
||||
// Options here
|
||||
}),
|
||||
],
|
||||
theme: {
|
||||
title: 'Vikunja',
|
||||
colors: {
|
||||
// https://histoire.dev/guide/config.html#builtin-colors
|
||||
gray: defaultColors.zinc,
|
||||
primary: defaultColors.cyan,
|
||||
},
|
||||
// logo: {
|
||||
// square: './img/square.png',
|
||||
// light: './img/light.png',
|
||||
// dark: './img/dark.png',
|
||||
// },
|
||||
// logoHref: 'https://acme.com',
|
||||
// favicon: './favicon.ico',
|
||||
},
|
||||
})
|
|
@ -9,13 +9,7 @@
|
|||
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="/images/icons/apple-touch-icon-180x180.png"/>
|
||||
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-700italic.woff2" as="font">
|
||||
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-italic.woff2" as="font">
|
||||
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-500.woff2" as="font">
|
||||
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-700.woff2" as="font">
|
||||
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-regular.woff2" as="font">
|
||||
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-700.woff2" as="font">
|
||||
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-regular.woff2" as="font">
|
||||
<!--__vite-plugin-inject-preload__-->
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
149
package.json
149
package.json
|
@ -1,63 +1,91 @@
|
|||
{
|
||||
"name": "vikunja-frontend",
|
||||
"version": "0.10.0",
|
||||
"description": "The todo app to organize your life.",
|
||||
"private": true,
|
||||
"version": "0.10.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://kolaente.dev/vikunja/frontend"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://kolaente.dev/vikunja/frontend/issues"
|
||||
},
|
||||
"homepage": "https://vikunja.io/",
|
||||
"funding": "https://opencollective.com/vikunja",
|
||||
"packageManager": "pnpm@7.26.0",
|
||||
"keywords": [
|
||||
"todo",
|
||||
"productivity",
|
||||
"task management",
|
||||
"organisation",
|
||||
"gantt",
|
||||
"kanban"
|
||||
],
|
||||
"scripts": {
|
||||
"serve": "vite",
|
||||
"serve:dist-dev": "node scripts/serve-dist.js",
|
||||
"serve:dist": "vite preview --port 4173",
|
||||
"preview": "vite preview --port 4173",
|
||||
"preview:dev": "vite preview --outDir dist-dev --mode development --port 4173",
|
||||
"build": "vite build && workbox copyLibraries dist/",
|
||||
"build:modern-only": "BUILD_MODERN_ONLY=true vite build && workbox copyLibraries dist/",
|
||||
"build:dev": "vite build -m development --outDir dist-dev/",
|
||||
"build:dev": "vite build --mode development --outDir dist-dev/",
|
||||
"lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts",
|
||||
"cypress:open": "cypress open",
|
||||
"test:unit": "vitest --run",
|
||||
"test:unit-watch": "vitest watch",
|
||||
"test:frontend": "cypress run",
|
||||
"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'",
|
||||
"test:e2e-dev": "start-server-and-test preview http://127.0.0.1:4173 'cypress open --e2e'",
|
||||
"test:unit": "vitest",
|
||||
"typecheck": "vue-tsc --noEmit && vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
|
||||
"browserslist:update": "npx browserslist@latest --update-db"
|
||||
"browserslist:update": "pnpm dlx browserslist@latest --update-db",
|
||||
"fonts:update": "pnpm fonts:download && pnpm fonts:subset",
|
||||
"fonts:download": "./scripts/fonts-download.sh",
|
||||
"fonts:subset": "./scripts/fonts-subset.sh",
|
||||
"story:dev": "histoire dev",
|
||||
"story:build": "histoire build",
|
||||
"story:preview": "histoire preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "6.2.1",
|
||||
"@fortawesome/free-regular-svg-icons": "6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.2.1",
|
||||
"@fortawesome/vue-fontawesome": "3.0.2",
|
||||
"@fortawesome/vue-fontawesome": "3.0.3",
|
||||
"@github/hotkey": "2.0.1",
|
||||
"@infectoone/vue-ganttastic": "2.1.2",
|
||||
"@kyvg/vue3-notification": "2.6.1",
|
||||
"@sentry/tracing": "7.20.0",
|
||||
"@sentry/vue": "7.20.0",
|
||||
"@infectoone/vue-ganttastic": "2.1.4",
|
||||
"@intlify/unplugin-vue-i18n": "0.8.1",
|
||||
"@kyvg/vue3-notification": "2.8.0",
|
||||
"@sentry/tracing": "7.33.0",
|
||||
"@sentry/vue": "7.33.0",
|
||||
"@types/is-touch-device": "1.0.0",
|
||||
"@types/lodash.clonedeep": "4.5.7",
|
||||
"@types/sortablejs": "1.15.0",
|
||||
"@vueuse/core": "9.5.0",
|
||||
"@vueuse/router": "9.5.0",
|
||||
"axios": "0.27.2",
|
||||
"@vueuse/core": "9.11.1",
|
||||
"axios": "1.2.4",
|
||||
"blurhash": "2.0.4",
|
||||
"bulma-css-variables": "0.9.33",
|
||||
"camel-case": "4.1.2",
|
||||
"codemirror": "5.65.9",
|
||||
"codemirror": "5.65.11",
|
||||
"date-fns": "2.29.3",
|
||||
"dayjs": "1.11.6",
|
||||
"dompurify": "2.4.1",
|
||||
"dayjs": "1.11.7",
|
||||
"dompurify": "2.4.3",
|
||||
"easymde": "2.18.0",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"flatpickr": "4.6.13",
|
||||
"flexsearch": "0.7.21",
|
||||
"floating-vue": "2.0.0-beta.20",
|
||||
"highlight.js": "11.6.0",
|
||||
"focus-within": "3.0.2",
|
||||
"highlight.js": "11.7.0",
|
||||
"is-touch-device": "1.0.1",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"marked": "4.2.2",
|
||||
"marked": "4.2.12",
|
||||
"minimist": "1.2.7",
|
||||
"pinia": "2.0.24",
|
||||
"pinia": "2.0.29",
|
||||
"register-service-worker": "1.7.2",
|
||||
"snake-case": "3.0.4",
|
||||
"sortablejs": "1.15.0",
|
||||
"ufo": "1.0.0",
|
||||
"ufo": "1.0.1",
|
||||
"vue": "3.2.45",
|
||||
"vue-advanced-cropper": "2.8.6",
|
||||
"vue-advanced-cropper": "2.8.8",
|
||||
"vue-flatpickr-component": "11.0.1",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-router": "4.1.6",
|
||||
|
@ -65,50 +93,55 @@
|
|||
"zhyswan-vuedraggable": "4.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@4tw/cypress-drag-drop": "2.2.1",
|
||||
"@cypress/vite-dev-server": "4.0.1",
|
||||
"@cypress/vue": "5.0.1",
|
||||
"@4tw/cypress-drag-drop": "2.2.3",
|
||||
"@cypress/vite-dev-server": "5.0.2",
|
||||
"@cypress/vue": "5.0.3",
|
||||
"@faker-js/faker": "7.6.0",
|
||||
"@histoire/plugin-screenshot": "0.12.4",
|
||||
"@histoire/plugin-vue": "0.12.4",
|
||||
"@rushstack/eslint-patch": "1.2.0",
|
||||
"@types/codemirror": "5.60.5",
|
||||
"@types/codemirror": "5.60.7",
|
||||
"@types/dompurify": "2.4.0",
|
||||
"@types/flexsearch": "0.7.3",
|
||||
"@types/focus-within": "1.0.1",
|
||||
"@types/lodash.debounce": "4.0.7",
|
||||
"@types/marked": "4.0.7",
|
||||
"@types/node": "18.11.9",
|
||||
"@types/marked": "4.0.8",
|
||||
"@types/node": "18.11.18",
|
||||
"@types/postcss-preset-env": "7.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.43.0",
|
||||
"@typescript-eslint/parser": "5.43.0",
|
||||
"@vitejs/plugin-legacy": "2.3.1",
|
||||
"@vitejs/plugin-vue": "3.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.49.0",
|
||||
"@typescript-eslint/parser": "5.49.0",
|
||||
"@vitejs/plugin-legacy": "3.0.1",
|
||||
"@vitejs/plugin-vue": "4.0.0",
|
||||
"@vue/eslint-config-typescript": "11.0.2",
|
||||
"@vue/test-utils": "2.2.3",
|
||||
"@vue/test-utils": "2.2.7",
|
||||
"@vue/tsconfig": "0.1.3",
|
||||
"autoprefixer": "10.4.13",
|
||||
"browserslist": "4.21.4",
|
||||
"caniuse-lite": "1.0.30001431",
|
||||
"caniuse-lite": "1.0.30001447",
|
||||
"csstype": "3.1.1",
|
||||
"cypress": "11.1.0",
|
||||
"esbuild": "0.15.14",
|
||||
"eslint": "8.28.0",
|
||||
"eslint-plugin-vue": "9.7.0",
|
||||
"express": "4.18.2",
|
||||
"happy-dom": "7.7.0",
|
||||
"netlify-cli": "12.1.1",
|
||||
"postcss": "8.4.19",
|
||||
"cypress": "12.4.0",
|
||||
"esbuild": "0.17.4",
|
||||
"eslint": "8.32.0",
|
||||
"eslint-plugin-vue": "9.9.0",
|
||||
"happy-dom": "8.1.5",
|
||||
"histoire": "0.12.4",
|
||||
"netlify-cli": "12.9.1",
|
||||
"postcss": "8.4.21",
|
||||
"postcss-easing-gradients": "3.0.1",
|
||||
"postcss-easings": "3.0.1",
|
||||
"postcss-preset-env": "7.8.3",
|
||||
"rollup": "3.3.0",
|
||||
"rollup-plugin-visualizer": "5.8.3",
|
||||
"sass": "1.56.1",
|
||||
"typescript": "4.9.3",
|
||||
"vite": "3.2.4",
|
||||
"vite-plugin-pwa": "0.13.3",
|
||||
"vite-svg-loader": "3.6.0",
|
||||
"vitest": "0.25.2",
|
||||
"vue-tsc": "1.0.9",
|
||||
"wait-on": "6.0.1",
|
||||
"rollup": "3.10.1",
|
||||
"rollup-plugin-visualizer": "5.9.0",
|
||||
"sass": "1.57.1",
|
||||
"start-server-and-test": "1.15.3",
|
||||
"typescript": "4.9.4",
|
||||
"vite": "4.0.4",
|
||||
"vite-plugin-inject-preload": "1.2.0",
|
||||
"vite-plugin-pwa": "0.14.1",
|
||||
"vite-svg-loader": "4.0.0",
|
||||
"vitest": "0.28.2",
|
||||
"vue-tsc": "1.0.24",
|
||||
"wait-on": "7.0.1",
|
||||
"workbox-cli": "6.5.4"
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"packageManager": "pnpm@7.16.1"
|
||||
}
|
||||
}
|
||||
|
|
4588
pnpm-lock.yaml
4588
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
bb46342a0a08105b340ba7976cff9d80ef89901120ec0639669caa70bb7d2dbc43e78b1f635a7654ab2456e8358c98a4 ./scripts/deploy-preview-netlify.js
|
|
@ -1,15 +1,18 @@
|
|||
const {exec} = require('child_process')
|
||||
const axios = require('axios')
|
||||
import { exec } from 'node:child_process'
|
||||
|
||||
function createSlug(string) {
|
||||
return String(string)
|
||||
.trim()
|
||||
.normalize('NFKD')
|
||||
.toLowerCase()
|
||||
.replace(/[.\s/]/g, '-')
|
||||
.replace(/[^A-Za-z\d-]/g, '')
|
||||
}
|
||||
|
||||
const BOT_USER_ID = 513
|
||||
const giteaToken = process.env.GITEA_TOKEN
|
||||
const siteId = process.env.NETLIFY_SITE_ID
|
||||
const branchSlug = String(process.env.DRONE_SOURCE_BRANCH)
|
||||
.trim()
|
||||
.normalize('NFKD')
|
||||
.toLowerCase()
|
||||
.replace(/[.\s/]/g, '-')
|
||||
.replace(/[^A-Za-z\d-]/g, '')
|
||||
const branchSlug = createSlug(process.env.DRONE_SOURCE_BRANCH)
|
||||
const prNumber = process.env.DRONE_PULL_REQUEST
|
||||
|
||||
const prIssueCommentsUrl = `https://kolaente.dev/api/v1/repos/vikunja/frontend/issues/${prNumber}/comments`
|
||||
|
@ -35,7 +38,7 @@ const promiseExec = cmd => {
|
|||
stdout = await promiseExec(`./node_modules/.bin/netlify deploy --alias ${alias}`)
|
||||
console.log(stdout)
|
||||
|
||||
const {data} = await axios.get(prIssueCommentsUrl)
|
||||
const data = await fetch(prIssueCommentsUrl).then(response => response.json())
|
||||
const hasComment = data.some(c => c.user.id === BOT_USER_ID)
|
||||
|
||||
if (hasComment) {
|
||||
|
@ -43,8 +46,7 @@ const promiseExec = cmd => {
|
|||
return
|
||||
}
|
||||
|
||||
await axios.post(prIssueCommentsUrl, {
|
||||
body: `
|
||||
const message = `
|
||||
Hi ${process.env.DRONE_COMMIT_AUTHOR}!
|
||||
|
||||
Thank you for creating a PR!
|
||||
|
@ -57,14 +59,25 @@ You will need to manually connect this to an api running somehwere. The easiest
|
|||
Have a nice day!
|
||||
|
||||
> Beep boop, I'm a bot.
|
||||
`,
|
||||
}, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'accept': 'application/json',
|
||||
'Authorization': `token ${giteaToken}`,
|
||||
},
|
||||
})
|
||||
`
|
||||
|
||||
console.log(`Preview comment sent successfully to PR #${prNumber}!`)
|
||||
try {
|
||||
const response = await fetch(prIssueCommentsUrl, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
body: message,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'accept': 'application/json',
|
||||
'Authorization': `token ${giteaToken}`,
|
||||
},
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error, status = ${response.status}`)
|
||||
}
|
||||
console.log(`Preview comment sent successfully to PR #${prNumber}!`)
|
||||
} catch (e) {
|
||||
console.log(`Could not send preview comment to PR #${prNumber}! ${e.message}`)
|
||||
}
|
||||
})()
|
|
@ -0,0 +1 @@
|
|||
57af69409e66bc87f4f2fc5822dd8d3c2eb47c601f81af1ac4a56f3e2d80837b1a2de06f4ff57695ec379b7c15b881e3 ./scripts/deploy-preview-netlify.mjs
|
|
@ -0,0 +1,56 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
#
|
||||
# This script downloads our original font files from their source repos
|
||||
# and puts them in our originalMedia folder.
|
||||
#
|
||||
|
||||
err_report() {
|
||||
echo "Error on line $(caller)" >&2
|
||||
}
|
||||
|
||||
trap err_report ERR
|
||||
|
||||
ORIGINAL_FONTS_DIR="./originalMedia/fonts"
|
||||
|
||||
# update these if there is a new version
|
||||
FONT_URLS=(
|
||||
"https://github.com/googlefonts/opensans/blob/27d060e1aad6886daeda67629ee28189f795f534/fonts/variable/OpenSans%5Bwdth%2Cwght%5D.ttf?raw=true"
|
||||
"https://github.com/googlefonts/opensans/blob/27d060e1aad6886daeda67629ee28189f795f534/fonts/variable/OpenSans-Italic%5Bwdth%2Cwght%5D.ttf?raw=true"
|
||||
"https://github.com/andrew-paglinawan/QuicksandFamily/blob/db6de44878582966f45a0debaef10d57108d93a7/fonts/Quicksand%5Bwght%5D.ttf?raw=true"
|
||||
)
|
||||
|
||||
|
||||
echo ""
|
||||
echo "###################################################"
|
||||
echo "# Download font files"
|
||||
echo "###################################################"
|
||||
echo ""
|
||||
|
||||
mkdir -p $ORIGINAL_FONTS_DIR
|
||||
|
||||
for URL in ${FONT_URLS[@]}; do
|
||||
wget -L $URL \
|
||||
--directory-prefix=$ORIGINAL_FONTS_DIR \
|
||||
--quiet \
|
||||
--timestamping \
|
||||
--show-progress
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "###################################################"
|
||||
echo "# Remove '?raw=true' filename suffix"
|
||||
echo "###################################################"
|
||||
echo ""
|
||||
|
||||
# Iterate over all files in directory with filetype ending in "?raw=true"
|
||||
for file in $ORIGINAL_FONTS_DIR/*?raw=true; do
|
||||
# Remove "?raw=true" from file name and store in variable
|
||||
new_name=$(echo $file | sed 's/?raw=true//')
|
||||
|
||||
# Overwrite existing file with new name
|
||||
mv -v $file $new_name
|
||||
done
|
||||
|
||||
echo "Renaming files complete"
|
|
@ -0,0 +1,161 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
#
|
||||
# This script subsets our variable fonts,
|
||||
# converts them to woff2 files and puts them in the
|
||||
# fonts folder.
|
||||
#
|
||||
# We do have to update the font paths in the @font-face
|
||||
# definitions manually since we use a checksum to make
|
||||
#
|
||||
# We use fonttools to create a partial instance of the
|
||||
# variable font where we keep only our needed features.
|
||||
# See more at:
|
||||
# https://fonttools.readthedocs.io/en/latest/varLib/instancer.html
|
||||
#
|
||||
# fonttools requires python > 3.7. For up-to-date
|
||||
# instructions see https://github.com/fonttools/fonttools#installation
|
||||
#
|
||||
# Lot's of info was gathered from:
|
||||
# https://markoskon.com/creating-font-subsets/
|
||||
# https://barrd.dev/article/create-a-variable-font-subset-for-smaller-file-size/
|
||||
#
|
||||
|
||||
ORIGINAL_FONTS="./originalMedia/fonts"
|
||||
TEMP_FOLDER="./.subset-fonts-temp"
|
||||
FONT_FOLDER="./src/assets/fonts"
|
||||
|
||||
err_report() {
|
||||
echo "Error on line $(caller)" >&2
|
||||
}
|
||||
|
||||
trap err_report ERR
|
||||
|
||||
mkdir -p $TEMP_FOLDER
|
||||
|
||||
# the latin subset that google uses on GoogleFonts
|
||||
# this is the same as the latin subset range that google uses on GoogleFonts
|
||||
# see for examle the unicode-range definition here:
|
||||
# https://fonts.googleapis.com/css2?family=Open+Sans
|
||||
UNICODE_LATIN_SUBSET="U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,\
|
||||
U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,\
|
||||
U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD"
|
||||
|
||||
get_filename_without_type() {
|
||||
filename=$1
|
||||
dirname=$(dirname $filename)
|
||||
# Extract the file type using parameter expansion
|
||||
filetype=${filename##*.}
|
||||
basename=$(basename $filename .$filetype)
|
||||
echo $basename
|
||||
}
|
||||
|
||||
# This function takes a font file and creates a subset of it using a specified set of unicode characters.
|
||||
instance_and_subset () {
|
||||
# Define default arguments for the subsetter.
|
||||
DEFAULT_SUBSETTER_ARGS="--layout-features=* --unicodes=${UNICODE_LATIN_SUBSET}"
|
||||
|
||||
# Assign function arguments to variables with more descriptive names.
|
||||
INPUT_FONT_FILE=$1
|
||||
INSTANCER_ARGS=$2
|
||||
OUTPUT_FONT_BASENAME=$3
|
||||
OUTPUT_FOLDER=$FONT_FOLDER
|
||||
|
||||
# If the output font basename is not provided, use the input font file's basename as the output font basename.
|
||||
if [ -z "$OUTPUT_FONT_BASENAME" ]; then
|
||||
INPUT_FONT_BASENAME=$(get_filename_without_type $INPUT_FONT_FILE)
|
||||
OUTPUT_FONT_BASENAME=$INPUT_FONT_BASENAME
|
||||
fi
|
||||
|
||||
# Use the default subsetter arguments if no custom arguments are provided.
|
||||
SUBSETTER_ARGS="${4:-$DEFAULT_SUBSETTER_ARGS}"
|
||||
|
||||
CHECKSUM=$(
|
||||
# Concatenate the contents of the input font file, the instancer arguments, and the subsetter arguments
|
||||
printf "%s%s" "$(cat $INPUT_FONT_FILE)" "$INSTANCER_ARGS" "$SUBSETTER_ARGS" |
|
||||
# Calculate the Blake2b checksum of the concatenated string
|
||||
b2sum |
|
||||
# Extract the checksum from the output of b2sum (it's the first field)
|
||||
awk '{print $1}'
|
||||
)
|
||||
|
||||
# Limit the checksum to 8 characters.
|
||||
CHECKSUM=$(echo "${CHECKSUM:0:8}")
|
||||
|
||||
# Construct the output font's filename
|
||||
OUTPUT_FONT_BASENAME="${OUTPUT_FONT_BASENAME}_${CHECKSUM}"
|
||||
OUTPUT_FONT_FILE="${OUTPUT_FOLDER}/${OUTPUT_FONT_BASENAME}.woff2"
|
||||
|
||||
# Check if the output font file already exists
|
||||
if test -f $OUTPUT_FONT_FILE; then
|
||||
echo "${OUTPUT_FONT_FILE} exists"
|
||||
return 0
|
||||
fi
|
||||
|
||||
FONT_INSTANCE="${TEMP_FOLDER}/${OUTPUT_FONT_BASENAME}.ttf"
|
||||
|
||||
if [ -n "$INSTANCER_ARGS" ]; then
|
||||
# If the INSTANCER_ARGS variable is set, use fonttools to create a font instance
|
||||
fonttools varLib.instancer --output $FONT_INSTANCE $INPUT_FONT_FILE $INSTANCER_ARGS
|
||||
else
|
||||
# Otherwise, just copy the input font file to the font instance file
|
||||
cp $INPUT_FONT_FILE $FONT_INSTANCE
|
||||
fi
|
||||
|
||||
# Use pyftsubset to create a subset of the font instance and save it to the output font file
|
||||
pyftsubset $FONT_INSTANCE --output-file=$OUTPUT_FONT_FILE --flavor=woff2 $SUBSETTER_ARGS
|
||||
|
||||
echo "${OUTPUT_FONT_BASENAME} subsetted."
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "###################################################"
|
||||
echo "# Install required libs"
|
||||
echo "###################################################"
|
||||
echo ""
|
||||
|
||||
pip install fonttools brotli
|
||||
|
||||
echo ""
|
||||
echo "###################################################"
|
||||
echo "# Create a partial instance of the variable font"
|
||||
echo "# where we keep only our needed features and then"
|
||||
echo "# subset fonts with latin unicode range and export"
|
||||
echo "# as woff2 file"
|
||||
echo "###################################################"
|
||||
echo ""
|
||||
|
||||
mkdir -p $TEMP_FOLDER
|
||||
|
||||
echo "\nOpen Sans"
|
||||
# we drop the wdth axis for all
|
||||
|
||||
instance_and_subset "${ORIGINAL_FONTS}/OpenSans[wdth,wght].ttf" "wdth=drop wght=400:700" "OpenSans[wght]"
|
||||
|
||||
# we restrict the wght range
|
||||
instance_and_subset "${ORIGINAL_FONTS}/OpenSans[wdth,wght].ttf" "wdth=drop wght=400" "OpenSans-Regular"
|
||||
instance_and_subset "${ORIGINAL_FONTS}/OpenSans[wdth,wght].ttf" "wdth=drop wght=700" "OpenSans-Bold"
|
||||
|
||||
echo "\nOpen Sans Italic"
|
||||
# we drop the wdth axis for all
|
||||
|
||||
instance_and_subset "${ORIGINAL_FONTS}/OpenSans-Italic[wdth,wght].ttf" "wdth=drop wght=400:700" "OpenSans-Italic[wght]"
|
||||
|
||||
# we restrict the wght range
|
||||
instance_and_subset "${ORIGINAL_FONTS}/OpenSans-Italic[wdth,wght].ttf" "wdth=drop wght=400" "OpenSans-RegularItalic"
|
||||
instance_and_subset "${ORIGINAL_FONTS}/OpenSans-Italic[wdth,wght].ttf" "wdth=drop wght=700" "OpenSans-BoldItalic"
|
||||
|
||||
echo "\nQuicksand"
|
||||
|
||||
instance_and_subset "${ORIGINAL_FONTS}/Quicksand[wght].ttf" "wght=400:700"
|
||||
|
||||
# we restrict the wght range
|
||||
instance_and_subset "${ORIGINAL_FONTS}/Quicksand[wght].ttf" "wght=400" "Quicksand-Regular"
|
||||
instance_and_subset "${ORIGINAL_FONTS}/Quicksand[wght].ttf" "wght=600" "Quicksand-SemiBold"
|
||||
instance_and_subset "${ORIGINAL_FONTS}/Quicksand[wght].ttf" "wght=700" "Quicksand-Bold"
|
||||
|
||||
echo "\nSubsetting files complete"
|
||||
|
||||
# remove temp folder
|
||||
rm -r $TEMP_FOLDER
|
|
@ -1,16 +0,0 @@
|
|||
const path = require('path')
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
|
||||
const p = path.join(__dirname, '..', 'dist-dev')
|
||||
const port = 4173
|
||||
|
||||
app.use(express.static(p))
|
||||
// Handle urls set by the frontend
|
||||
app.get('*', (request, response, next) => {
|
||||
response.sendFile(`${p}/index.html`)
|
||||
})
|
||||
app.listen(port, '127.0.0.1', () => {
|
||||
console.log(`Serving files from ${p}`)
|
||||
console.log(`Server started on port ${port}`)
|
||||
})
|
18
src/App.vue
18
src/App.vue
|
@ -15,9 +15,8 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, watch, type Ref} from 'vue'
|
||||
import {useRouter} from 'vue-router'
|
||||
import {useRouteQuery} from '@vueuse/router'
|
||||
import {computed, watch} from 'vue'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import isTouchDevice from 'is-touch-device'
|
||||
import {success} from '@/message'
|
||||
|
@ -41,6 +40,7 @@ import {useAuthStore} from './stores/auth'
|
|||
const baseStore = useBaseStore()
|
||||
const authStore = useAuthStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
useBodyClass('is-touch', isTouchDevice())
|
||||
const keyboardShortcutsActive = computed(() => baseStore.keyboardShortcutsActive)
|
||||
|
@ -51,9 +51,9 @@ const authLinkShare = computed(() => authStore.authLinkShare)
|
|||
const {t} = useI18n({useScope: 'global'})
|
||||
|
||||
// setup account deletion verification
|
||||
const accountDeletionConfirm = useRouteQuery('accountDeletionConfirm') as Ref<null | string>
|
||||
const accountDeletionConfirm = computed(() => route.query?.accountDeletionConfirm as (string | undefined))
|
||||
watch(accountDeletionConfirm, async (accountDeletionConfirm) => {
|
||||
if (accountDeletionConfirm === null) {
|
||||
if (accountDeletionConfirm === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -64,9 +64,9 @@ watch(accountDeletionConfirm, async (accountDeletionConfirm) => {
|
|||
}, { immediate: true })
|
||||
|
||||
// setup password reset redirect
|
||||
const userPasswordReset = useRouteQuery('userPasswordReset') as Ref<null | string>
|
||||
const userPasswordReset = computed(() => route.query?.userPasswordReset as (string | undefined))
|
||||
watch(userPasswordReset, (userPasswordReset) => {
|
||||
if (userPasswordReset === null) {
|
||||
if (userPasswordReset === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -75,9 +75,9 @@ watch(userPasswordReset, (userPasswordReset) => {
|
|||
}, { immediate: true })
|
||||
|
||||
// setup email verification redirect
|
||||
const userEmailConfirm = useRouteQuery('userEmailConfirm') as Ref<null | string>
|
||||
const userEmailConfirm = computed(() => route.query?.userEmailConfirm as (string | undefined))
|
||||
watch(userEmailConfirm, (userEmailConfirm) => {
|
||||
if (userEmailConfirm === null) {
|
||||
if (userEmailConfirm === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,58 @@
|
|||
<script lang="ts" setup>
|
||||
import {logEvent} from 'histoire/client'
|
||||
import {reactive} from 'vue'
|
||||
import {createRouter, createMemoryHistory} from 'vue-router'
|
||||
import BaseButton from './BaseButton.vue'
|
||||
|
||||
function setupApp({ app }) {
|
||||
// Router mock
|
||||
app.use(createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', name: 'home', component: { render: () => null } },
|
||||
],
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
const state = reactive({
|
||||
disabled: false,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story :setup-app="setupApp" :layout="{ type: 'grid', width: '200px' }">
|
||||
<Variant title="custom">
|
||||
<template #controls>
|
||||
<HstCheckbox v-model="state.disabled" title="Disabled" />
|
||||
</template>
|
||||
<BaseButton :disabled="state.disabled">
|
||||
Hello!
|
||||
</BaseButton>
|
||||
</Variant>
|
||||
|
||||
<Variant title="disabled">
|
||||
<BaseButton disabled>
|
||||
Hello!
|
||||
</BaseButton>
|
||||
</Variant>
|
||||
|
||||
<Variant title="router link">
|
||||
<BaseButton :to="'home'">
|
||||
Hello!
|
||||
</BaseButton>
|
||||
</Variant>
|
||||
|
||||
<Variant title="external link">
|
||||
<BaseButton href="https://vikunja.io">
|
||||
Hello!
|
||||
</BaseButton>
|
||||
</Variant>
|
||||
|
||||
<Variant title="button">
|
||||
<BaseButton @click="logEvent('Click', $event)">
|
||||
Hello!
|
||||
</BaseButton>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
|
@ -61,12 +61,12 @@ export type BaseButtonTypes = typeof BASE_BUTTON_TYPES_MAP[keyof typeof BASE_BUT
|
|||
|
||||
import {unrefElement} from '@vueuse/core'
|
||||
import {ref, type HTMLAttributes} from 'vue'
|
||||
import type {RouteLocationNamedRaw} from 'vue-router'
|
||||
import type {RouteLocationRaw} from 'vue-router'
|
||||
|
||||
export interface BaseButtonProps extends HTMLAttributes {
|
||||
type?: BaseButtonTypes
|
||||
disabled?: boolean
|
||||
to?: RouteLocationNamedRaw
|
||||
to?: RouteLocationRaw
|
||||
href?: string
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
<template>
|
||||
<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">
|
||||
// the logic of this component is loosly based on this article
|
||||
// https://gomakethings.com/how-to-add-transition-animations-to-vanilla-javascript-show-and-hide-methods/#putting-it-all-together
|
||||
|
||||
import {computed, ref} from 'vue'
|
||||
import {getInheritedBackgroundColor} from '@/helpers/getInheritedBackgroundColor'
|
||||
|
||||
const props = defineProps({
|
||||
/** Wheather the Expandable is open or not */
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/** If there is too much content, content will be cut of here. */
|
||||
initialHeight: {
|
||||
type: Number,
|
||||
default: undefined,
|
||||
},
|
||||
/** The hidden content is indicated by a gradient. This is the color that the gradient fades to.
|
||||
* Makes only sense if `initialHeight` is set. */
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
},
|
||||
})
|
||||
|
||||
const wrapper = ref<HTMLElement | null>(null)
|
||||
|
||||
const computedBackgroundColor = computed(() => {
|
||||
if (wrapper.value === null) {
|
||||
return props.backgroundColor || '#fff'
|
||||
}
|
||||
return props.backgroundColor || getInheritedBackgroundColor(wrapper.value)
|
||||
})
|
||||
|
||||
/**
|
||||
* Get the natural height of the element
|
||||
*/
|
||||
function getHeight(el: HTMLElement) {
|
||||
const { display } = el.style // save display property
|
||||
el.style.display = 'block' // Make it visible
|
||||
const height = `${el.scrollHeight}px` // Get its height
|
||||
el.style.display = display // revert to original display property
|
||||
return height
|
||||
}
|
||||
|
||||
/**
|
||||
* force layout of element changes
|
||||
* https://gist.github.com/paulirish/5d52fb081b3570c81e3a
|
||||
*/
|
||||
function forceLayout(el: HTMLElement) {
|
||||
el.offsetTop
|
||||
}
|
||||
|
||||
/* ######################################################################
|
||||
# The following functions are called by the js hooks of the transitions.
|
||||
# They follow the orignal hook order of the vue transition component
|
||||
# see: https://vuejs.org/guide/built-ins/transition.html#javascript-hooks
|
||||
###################################################################### */
|
||||
|
||||
function beforeEnter(el: HTMLElement) {
|
||||
el.style.height = '0'
|
||||
el.style.willChange = 'height'
|
||||
el.style.backfaceVisibility = 'hidden'
|
||||
forceLayout(el)
|
||||
}
|
||||
|
||||
// the done callback is optional when
|
||||
// used in combination with CSS
|
||||
function enter(el: HTMLElement) {
|
||||
const height = getHeight(el) // Get the natural height
|
||||
el.style.height = height // Update the height
|
||||
}
|
||||
|
||||
function afterEnter(el: HTMLElement) {
|
||||
removeHeight(el)
|
||||
}
|
||||
|
||||
function enterCancelled(el: HTMLElement) {
|
||||
removeHeight(el)
|
||||
}
|
||||
|
||||
function beforeLeave(el: HTMLElement) {
|
||||
// Give the element a height to change from
|
||||
el.style.height = `${el.scrollHeight}px`
|
||||
forceLayout(el)
|
||||
}
|
||||
|
||||
function leave(el: HTMLElement) {
|
||||
// Set the height back to 0
|
||||
el.style.height = '0'
|
||||
el.style.willChange = ''
|
||||
el.style.backfaceVisibility = ''
|
||||
}
|
||||
|
||||
function afterLeave(el: HTMLElement) {
|
||||
removeHeight(el)
|
||||
}
|
||||
|
||||
function leaveCancelled(el: HTMLElement) {
|
||||
removeHeight(el)
|
||||
}
|
||||
|
||||
function removeHeight(el: HTMLElement) {
|
||||
el.style.height = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$transition-time: 300ms;
|
||||
|
||||
.expandable-slide-enter-active,
|
||||
.expandable-slide-leave-active {
|
||||
transition:
|
||||
opacity $transition-time ease-in-quint,
|
||||
height $transition-time ease-in-out-quint;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.expandable-slide-enter,
|
||||
.expandable-slide-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.expandable-initial-height {
|
||||
padding: 5px;
|
||||
margin: -5px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: block;
|
||||
background-image: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
ease-in-out
|
||||
v-bind(computedBackgroundColor)
|
||||
);
|
||||
position: absolute;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.expandable-initial-height--expanded {
|
||||
height: 100% !important;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,16 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import {computed} from 'vue'
|
||||
import {useNow} from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { useNow } from '@vueuse/core'
|
||||
|
||||
import LogoFull from '@/assets/logo-full.svg?url'
|
||||
import LogoFullPride from '@/assets/logo-full-pride.svg?url'
|
||||
import LogoFull from '@/assets/logo-full.svg?component'
|
||||
import LogoFullPride from '@/assets/logo-full-pride.svg?component'
|
||||
|
||||
const now = useNow()
|
||||
const logoUrl = computed(() => now.value.getMonth() === 5 ? LogoFullPride : LogoFull)
|
||||
const Logo = computed(() => now.value.getMonth() === 5 ? LogoFullPride : LogoFull)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img alt="Vikunja" :src="logoUrl" class="logo" />
|
||||
<Logo alt="Vikunja" class="logo" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
<template>
|
||||
<div class="content-auth">
|
||||
<BaseButton
|
||||
v-if="menuActive"
|
||||
v-show="menuActive"
|
||||
@click="baseStore.setMenuActive(false)"
|
||||
class="menu-hide-button d-print-none"
|
||||
>
|
||||
<icon icon="times"/>
|
||||
</BaseButton>
|
||||
<div
|
||||
class="app-container"
|
||||
:class="{'has-background': background || blurHash}"
|
||||
:style="{'background-image': blurHash && `url(${blurHash})`}"
|
||||
class="app-container"
|
||||
>
|
||||
<div
|
||||
:class="{'is-visible': background}"
|
||||
|
@ -18,14 +18,14 @@
|
|||
:style="{'background-image': background && `url(${background})`}"></div>
|
||||
<navigation class="d-print-none"/>
|
||||
<main
|
||||
class="app-content"
|
||||
:class="[
|
||||
{ 'is-menu-enabled': menuActive },
|
||||
$route.name,
|
||||
]"
|
||||
class="app-content"
|
||||
>
|
||||
<BaseButton
|
||||
v-if="menuActive"
|
||||
v-show="menuActive"
|
||||
@click="baseStore.setMenuActive(false)"
|
||||
class="mobile-overlay d-print-none"
|
||||
/>
|
||||
|
@ -143,7 +143,6 @@ labelStore.loadAllLabels()
|
|||
|
||||
&:hover,
|
||||
&:focus {
|
||||
height: 1rem;
|
||||
color: var(--grey-600);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -435,7 +435,7 @@ $vikunja-nav-selected-width: 0.4rem;
|
|||
|
||||
.menu-list {
|
||||
li {
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
font-family: $vikunja-font;
|
||||
}
|
||||
|
||||
|
@ -460,7 +460,7 @@ $vikunja-nav-selected-width: 0.4rem;
|
|||
font-weight: bold;
|
||||
font-family: $vikunja-font;
|
||||
color: $vikunja-nav-color;
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
min-height: 2.5rem;
|
||||
padding-top: 0;
|
||||
padding-left: $navbar-padding;
|
||||
|
@ -479,6 +479,8 @@ $vikunja-nav-selected-width: 0.4rem;
|
|||
.count {
|
||||
color: var(--grey-500);
|
||||
margin-right: .5rem;
|
||||
// align brackets with number
|
||||
font-feature-settings: "case";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<script lang="ts" setup>
|
||||
import {logEvent} from 'histoire/client'
|
||||
import XButton from './button.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story :layout="{ type: 'grid', width: '200px' }">
|
||||
<Variant title="primary">
|
||||
<XButton @click="logEvent('Click', $event)" variant="primary">
|
||||
Order pizza!
|
||||
</XButton>
|
||||
</Variant>
|
||||
|
||||
<Variant title="secondary">
|
||||
<XButton @click="logEvent('Click', $event)" variant="secondary">
|
||||
Order spaghetti!
|
||||
</XButton>
|
||||
</Variant>
|
||||
|
||||
<Variant title="tertiary">
|
||||
<XButton @click="logEvent('Click', $event)" variant="tertiary">
|
||||
Order tortellini!
|
||||
</XButton>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
|
@ -0,0 +1,14 @@
|
|||
<script lang="ts" setup>
|
||||
import {reactive} from 'vue'
|
||||
import ColorPicker from './ColorPicker.vue'
|
||||
|
||||
const state = reactive({
|
||||
color: '#f2f2f2',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story :layout="{ type: 'grid', width: '200px' }">
|
||||
<ColorPicker v-model="state.color" />
|
||||
</Story>
|
||||
</template>
|
|
@ -37,6 +37,7 @@
|
|||
<script setup lang="ts">
|
||||
import {computed, ref, toRef, watch} from 'vue'
|
||||
import {createRandomID} from '@/helpers/randomId'
|
||||
import XButton from '@/components/input/button.vue'
|
||||
|
||||
const DEFAULT_COLORS = [
|
||||
'#1973ff',
|
|
@ -0,0 +1,11 @@
|
|||
<script lang="ts" setup>
|
||||
import Card from './card.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story :layout="{ type: 'grid', width: '200px' }">
|
||||
<Card>
|
||||
Card content
|
||||
</Card>
|
||||
</Story>
|
||||
</template>
|
|
@ -98,5 +98,6 @@ function setSubscriptionInStore(sub: ISubscription) {
|
|||
<style scoped lang="scss">
|
||||
.dropdown-trigger {
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
|
@ -546,7 +546,7 @@ function select(parentIndex: number, index: number) {
|
|||
}
|
||||
let elems = resultRefs.value[parentIndex][index]
|
||||
if (results.value[parentIndex].items.length === index) {
|
||||
elems = resultRefs.value[parentIndex + 1][0]
|
||||
elems = resultRefs.value[parentIndex + 1] ? resultRefs.value[parentIndex + 1][0] : undefined
|
||||
}
|
||||
if (
|
||||
typeof elems === 'undefined'
|
||||
|
@ -576,6 +576,8 @@ function reset() {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.quick-actions {
|
||||
overflow: hidden;
|
||||
|
||||
// FIXME: changed position should be an option of the modal
|
||||
:deep(.modal-content) {
|
||||
top: 3rem;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Loading
|
||||
v-if="props.isLoading && tasks.size || dayjsLanguageLoading"
|
||||
v-if="props.isLoading && !ganttBars.length || dayjsLanguageLoading"
|
||||
class="gantt-container"
|
||||
/>
|
||||
<div class="gantt-container" v-else>
|
||||
|
@ -16,14 +16,14 @@
|
|||
@dblclick-bar="openTask"
|
||||
:width="ganttChartWidth + 'px'"
|
||||
>
|
||||
<template #timeunit="{label, value}">
|
||||
<template #timeunit="{value, date}">
|
||||
<div
|
||||
class="timeunit-wrapper"
|
||||
:class="{'today': dayIsToday(label)}"
|
||||
:class="{'today': dateIsToday(date)}"
|
||||
>
|
||||
<span>{{ value }}</span>
|
||||
<span class="weekday">
|
||||
{{ weekdayFromTimeLabel(label) }}
|
||||
{{ weekDayFromDate(date) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -40,9 +40,7 @@
|
|||
<script setup lang="ts">
|
||||
import {computed, ref, watch, toRefs} from 'vue'
|
||||
import {useRouter} from 'vue-router'
|
||||
import {format, parse} from 'date-fns'
|
||||
import dayjs from 'dayjs'
|
||||
import isToday from 'dayjs/plugin/isToday'
|
||||
import {useNow} from '@vueuse/core'
|
||||
|
||||
import {getHexColor} from '@/models/task'
|
||||
|
||||
|
@ -63,6 +61,7 @@ import {
|
|||
|
||||
import Loading from '@/components/misc/loading.vue'
|
||||
import {MILLISECONDS_A_DAY} from '@/constants/date'
|
||||
import {useWeekDayFromDate} from '@/helpers/time/formatDate'
|
||||
|
||||
export interface GanttChartProps {
|
||||
isLoading: boolean,
|
||||
|
@ -85,7 +84,6 @@ const {tasks, filters} = toRefs(props)
|
|||
// setup dayjs for vue-ganttastic
|
||||
const dayjsLanguageLoading = ref(false)
|
||||
// const dayjsLanguageLoading = useDayjsLanguageSync(dayjs)
|
||||
dayjs.extend(isToday)
|
||||
extendDayjs()
|
||||
|
||||
const router = useRouter()
|
||||
|
@ -157,23 +155,16 @@ function openTask(e: {
|
|||
})
|
||||
}
|
||||
|
||||
function parseTimeLabel(label: string) {
|
||||
return parse(label, 'dd.MMM', dateFromDate.value)
|
||||
}
|
||||
const weekDayFromDate = useWeekDayFromDate()
|
||||
|
||||
function weekdayFromTimeLabel(label: string): string {
|
||||
const parsed = parseTimeLabel(label)
|
||||
return format(parsed, 'E')
|
||||
}
|
||||
|
||||
function dayIsToday(label: string): boolean {
|
||||
const parsed = parseTimeLabel(label)
|
||||
|
||||
const today = new Date()
|
||||
return parsed.getDate() === today.getDate() &&
|
||||
parsed.getMonth() === today.getMonth() &&
|
||||
parsed.getFullYear() === today.getFullYear()
|
||||
}
|
||||
const today = useNow()
|
||||
const dateIsToday = computed(() => (date: Date) => {
|
||||
return (
|
||||
date.getDate() === today.value.getDate() &&
|
||||
date.getMonth() === today.value.getMonth() &&
|
||||
date.getFullYear() === today.value.getFullYear()
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<template>
|
||||
<div class="task-add">
|
||||
<div class="field is-grouped">
|
||||
<div class="task-add" ref="taskAdd">
|
||||
<div class="add-task__field field is-grouped">
|
||||
<p class="control has-icons-left is-expanded">
|
||||
<textarea
|
||||
:disabled="loading || undefined"
|
||||
class="add-task-textarea input"
|
||||
:class="{'textarea-empty': newTaskTitle === ''}"
|
||||
:placeholder="$t('list.list.addPlaceholder')"
|
||||
|
@ -33,25 +32,33 @@
|
|||
</x-button>
|
||||
</p>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="errorMessage !== ''">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
<quick-add-magic v-else/>
|
||||
<Expandable :open="errorMessage !== '' || taskAddFocused || taskAddHovered && debouncedTaskAddHovered">
|
||||
<p class="pt-3 mt-0 help is-danger" v-if="errorMessage !== ''">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
<quick-add-magic v-else class="quick-add-magic" />
|
||||
</Expandable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, ref} from 'vue'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {refDebounced, useElementHover, useFocusWithin} from '@vueuse/core'
|
||||
|
||||
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
|
||||
import {RELATION_KIND} from '@/types/IRelationKind'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
|
||||
import Expandable from '@/components/base/Expandable.vue'
|
||||
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
|
||||
import {parseSubtasksViaIndention} from '@/helpers/parseSubtasksViaIndention'
|
||||
import TaskRelationService from '@/services/taskRelation'
|
||||
import TaskRelationModel from '@/models/taskRelation'
|
||||
import {RELATION_KIND} from '@/types/IRelationKind'
|
||||
import {getLabelsFromPrefix} from '@/modules/parseTaskText'
|
||||
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import {useTaskStore} from '@/stores/tasks'
|
||||
|
||||
import {useAutoHeightTextarea} from '@/composables/useAutoHeightTextarea'
|
||||
|
||||
const props = defineProps({
|
||||
|
@ -70,9 +77,24 @@ const {t} = useI18n({useScope: 'global'})
|
|||
const authStore = useAuthStore()
|
||||
const taskStore = useTaskStore()
|
||||
|
||||
const taskAdd = ref<HTMLTextAreaElement | null>(null)
|
||||
|
||||
// enable only if we don't have a modal
|
||||
// onStartTyping(() => {
|
||||
// if (newTaskInput.value === null || document.activeElement === newTaskInput.value) {
|
||||
// return
|
||||
// }
|
||||
// newTaskInput.value.focus()
|
||||
// })
|
||||
|
||||
const { focused: taskAddFocused } = useFocusWithin(taskAdd)
|
||||
|
||||
const taskAddHovered = useElementHover(taskAdd)
|
||||
const debouncedTaskAddHovered = refDebounced(taskAddHovered, 500)
|
||||
|
||||
const errorMessage = ref('')
|
||||
|
||||
function resetEmptyTitleError(e) {
|
||||
function resetEmptyTitleError(e: KeyboardEvent) {
|
||||
if (
|
||||
(e.which <= 90 && e.which >= 48 || e.which >= 96 && e.which <= 105)
|
||||
&& newTaskTitle.value !== ''
|
||||
|
@ -82,6 +104,7 @@ function resetEmptyTitleError(e) {
|
|||
}
|
||||
|
||||
const loading = computed(() => taskStore.isLoading)
|
||||
|
||||
async function addTask() {
|
||||
if (newTaskTitle.value === '') {
|
||||
errorMessage.value = t('list.create.addTitleRequired')
|
||||
|
@ -98,14 +121,27 @@ async function addTask() {
|
|||
// by quick add magic.
|
||||
const createdTasks: { [key: ITask['title']]: ITask } = {}
|
||||
const tasksToCreate = parseSubtasksViaIndention(newTaskTitle.value)
|
||||
const newTasks = tasksToCreate.map(async ({title}) => {
|
||||
|
||||
// We ensure all labels exist prior to passing them down to the create task method
|
||||
// In the store it will only ever see one task at a time so there's no way to reliably
|
||||
// check if a new label was created before (because everything happens async).
|
||||
const allLabels = tasksToCreate.map(({title}) => getLabelsFromPrefix(title) ?? [])
|
||||
await taskStore.ensureLabelsExist(allLabels.flat())
|
||||
|
||||
const newTasks = tasksToCreate.map(async ({title, list}) => {
|
||||
if (title === '') {
|
||||
return
|
||||
}
|
||||
|
||||
// If the task has a list specified, make sure to use it
|
||||
let listId = null
|
||||
if (list !== null) {
|
||||
listId = await taskStore.findListId({list, listId: 0})
|
||||
}
|
||||
|
||||
const task = await taskStore.createNewTask({
|
||||
title,
|
||||
listId: authStore.settings.defaultListId,
|
||||
listId: listId || authStore.settings.defaultListId,
|
||||
position: props.defaultPosition,
|
||||
})
|
||||
createdTasks[title] = task
|
||||
|
@ -177,7 +213,9 @@ defineExpose({
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.task-add {
|
||||
.task-add,
|
||||
// overwrite bulma styles
|
||||
.task-add .add-task__field {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
@ -205,4 +243,8 @@ defineExpose({
|
|||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.quick-add-magic {
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref,computed, watch, type PropType} from 'vue'
|
||||
import {ref, computed, watch, type PropType} from 'vue'
|
||||
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import Editor from '@/components/input/AsyncEditor'
|
||||
|
@ -67,7 +67,7 @@ const taskStore = useTaskStore()
|
|||
const loading = computed(() => taskStore.isLoading)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
props.modelValue,
|
||||
(value) => {
|
||||
task.value = value
|
||||
},
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<template>
|
||||
<div class="heading">
|
||||
<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)"
|
||||
class="mt-1 ml-2"
|
||||
/>
|
||||
<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"/>
|
||||
<ColorBubble
|
||||
v-if="task.hexColor !== ''"
|
||||
:color="getHexColor(task.hexColor)"
|
||||
class="ml-2"
|
||||
/>
|
||||
</div>
|
||||
<h1
|
||||
class="title input"
|
||||
:class="{'disabled': !canWrite}"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-if="available">
|
||||
<div v-if="mode !== 'disabled' && prefixes !== undefined">
|
||||
<p class="help has-text-grey">
|
||||
{{ $t('task.quickAddMagic.hint') }}.
|
||||
<ButtonLink @click="() => visible = true">{{ $t('task.quickAddMagic.what') }}</ButtonLink>
|
||||
|
@ -100,6 +100,5 @@ import {PREFIXES} from '@/modules/parseTaskText'
|
|||
const visible = ref(false)
|
||||
const mode = ref(getQuickAddMagicMode())
|
||||
|
||||
const available = computed(() => mode.value !== 'disabled')
|
||||
const prefixes = computed(() => PREFIXES[mode.value])
|
||||
</script>
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
<select v-model="newTaskRelation.kind">
|
||||
<option value="unset">{{ $t('task.relation.select') }}</option>
|
||||
<option :key="`option_${rk}`" :value="rk" v-for="rk in RELATION_KINDS">
|
||||
{{ $tc(`task.relation.kinds.${rk}`, 1) }}
|
||||
{{ $t(`task.relation.kinds.${rk}`, 1) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
/>
|
||||
|
||||
<ColorBubble
|
||||
v-if="showListColor && listColor !== ''"
|
||||
v-if="showListColor && listColor !== '' && currentList.id !== task.listId"
|
||||
:color="listColor"
|
||||
class="mr-1"
|
||||
/>
|
||||
|
||||
<router-link
|
||||
:to="taskDetailRoute"
|
||||
:class="{ 'done': task.done}"
|
||||
:class="{ 'done': task.done, 'show-list': showList && taskList !== null}"
|
||||
class="tasktext"
|
||||
>
|
||||
<span>
|
||||
|
@ -38,7 +38,7 @@
|
|||
<template v-for="(pt, i) in task.relatedTasks.parenttask">
|
||||
{{ pt.title }}<template v-if="(i + 1) < task.relatedTasks.parenttask.length">, </template>
|
||||
</template>
|
||||
>
|
||||
›
|
||||
</span>
|
||||
{{ task.title }}
|
||||
</span>
|
||||
|
@ -71,7 +71,7 @@
|
|||
class="is-italic"
|
||||
:aria-expanded="showDefer ? 'true' : 'false'"
|
||||
>
|
||||
- {{ $t('task.detail.due', {at: formatDateSince(task.dueDate)}) }}
|
||||
– {{ $t('task.detail.due', {at: formatDateSince(task.dueDate)}) }}
|
||||
</time>
|
||||
</BaseButton>
|
||||
<CustomTransition name="fade">
|
||||
|
@ -391,6 +391,10 @@ function hideDeferDueDatePopup(e) {
|
|||
width: auto;
|
||||
}
|
||||
|
||||
.show-list .parent-tasks {
|
||||
padding-left: .25rem;
|
||||
}
|
||||
|
||||
.remove {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ import {debouncedWatch, tryOnMounted, useWindowSize, type MaybeRef} from '@vueus
|
|||
// TODO: also add related styles
|
||||
// OR: replace with vueuse function
|
||||
export function useAutoHeightTextarea(value: MaybeRef<string>) {
|
||||
const textarea = ref<HTMLInputElement>()
|
||||
const textarea = ref<HTMLTextAreaElement | null>(null)
|
||||
const minHeight = ref(0)
|
||||
|
||||
// adapted from https://github.com/LeaVerou/stretchy/blob/47f5f065c733029acccb755cae793009645809e2/src/stretchy.js#L34
|
||||
function resize(textareaEl: HTMLInputElement | undefined) {
|
||||
function resize(textareaEl: HTMLTextAreaElement | null) {
|
||||
if (!textareaEl) return
|
||||
|
||||
let empty
|
||||
|
|
|
@ -3,12 +3,11 @@ import {useOnline as useNetworkOnline} from '@vueuse/core'
|
|||
import type {ConfigurableWindow} from '@vueuse/core'
|
||||
|
||||
export function useOnline(options?: ConfigurableWindow) {
|
||||
const isOnline = useNetworkOnline(options)
|
||||
const fakeOnlineState = !!import.meta.env.VITE_IS_ONLINE
|
||||
if (fakeOnlineState) {
|
||||
if (isOnline.value === false && fakeOnlineState) {
|
||||
console.log('Setting fake online state', fakeOnlineState)
|
||||
return ref(true)
|
||||
}
|
||||
|
||||
return fakeOnlineState
|
||||
? ref(true)
|
||||
: useNetworkOnline(options)
|
||||
return isOnline
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export const URL_PREFIX = '/api/v1' // _without_ slash at the end
|
|
@ -0,0 +1,24 @@
|
|||
function getDefaultBackground() {
|
||||
const div = document.createElement('div')
|
||||
document.head.appendChild(div)
|
||||
const bg = window.getComputedStyle(div).backgroundColor
|
||||
document.head.removeChild(div)
|
||||
return bg
|
||||
}
|
||||
|
||||
// get default style for current browser
|
||||
const defaultStyle = getDefaultBackground() // typically "rgba(0, 0, 0, 0)"
|
||||
|
||||
// based on https://stackoverflow.com/a/62630563/15522256
|
||||
export function getInheritedBackgroundColor(el: HTMLElement): string {
|
||||
const backgroundColor = window.getComputedStyle(el).backgroundColor
|
||||
|
||||
if (backgroundColor !== defaultStyle) return backgroundColor
|
||||
|
||||
if (!el.parentElement) {
|
||||
// we reached the top parent el without getting an explicit color
|
||||
return defaultStyle
|
||||
}
|
||||
|
||||
return getInheritedBackgroundColor(el.parentElement)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue