forked from vikunja/frontend
Compare commits
144 Commits
1f25386f54
...
f9dad79b23
Author | SHA1 | Date |
---|---|---|
renovate | f9dad79b23 | |
renovate | 30f5cb0656 | |
renovate | 3f58c983da | |
kolaente | 8fa8b03aa6 | |
Yurii Vlasov | e4499f44b7 | |
kolaente | b799233bca | |
renovate | be0ae4bc29 | |
renovate | 60d99f3bba | |
renovate | fa666d2817 | |
renovate | 9312aa14fa | |
renovate | 68e4f776b9 | |
renovate | 2d137d564e | |
Frederick [Bot] | fc8824d942 | |
renovate | 6d4ca57601 | |
renovate | d2bf4e38b1 | |
renovate | a5f6857a40 | |
renovate | ed3d79fa4c | |
Frederick [Bot] | 81c5c54aed | |
renovate | 793e06c6ac | |
Nikola Sivkov v2 | 7eb07e92f8 | |
renovate | 2a15878b81 | |
renovate | ebd2b1e8c0 | |
renovate | d11fcfa072 | |
Frederick [Bot] | 8e6e976867 | |
kolaente | 9adf1aba89 | |
kolaente | e67088fdb7 | |
kolaente | da241d21f3 | |
kolaente | 97133010af | |
kolaente | 4576da0dd3 | |
renovate | fd4a68daf0 | |
renovate | 6f02d43801 | |
konrad | 2be784766f | |
Dominik Pschenitschni | 13a39be3de | |
renovate | d2e07efc7d | |
renovate | a44299e786 | |
renovate | 221f73c347 | |
renovate | 9b170d0d81 | |
kolaente | 16e61a8492 | |
kolaente | a95f1090d7 | |
kolaente | c6026107fa | |
renovate | e07e6bf677 | |
Dominik Pschenitschni | c6ed925424 | |
Dominik Pschenitschni | 7ed1a37de5 | |
renovate | 1a2e9af88f | |
renovate | f5e90067f6 | |
renovate | 188ae57dc0 | |
renovate | 3e4bbd58a3 | |
renovate | bb8ee15a2d | |
renovate | 4c46ae5b2f | |
renovate | ff27030e1c | |
renovate | 639e5e3d23 | |
Frederick [Bot] | e49e9352e5 | |
renovate | 705afa0272 | |
renovate | 83864a6bac | |
renovate | 664a39b70d | |
Dominik Pschenitschni | 9922fcba65 | |
Dominik Pschenitschni | 489014944a | |
Frederick [Bot] | bb44beb4ba | |
renovate | d996f24028 | |
renovate | 59cac0eb38 | |
Frederick [Bot] | 7adc5ceb9f | |
renovate | a3d9cb5324 | |
Frederick [Bot] | 6c192b6f59 | |
konrad | 8ff1b3006b | |
renovate | 0414352b02 | |
Dominik Pschenitschni | 2a2c27af92 | |
Dominik Pschenitschni | e1b35ff023 | |
renovate | 95b2bcf5fb | |
renovate | da26ec7f1c | |
Dominik Pschenitschni | 14466bf9b7 | |
Dominik Pschenitschni | 903e9a9904 | |
Dominik Pschenitschni | 56fd25e888 | |
renovate | c815830700 | |
renovate | 69f398f789 | |
renovate | 8280bd6bf5 | |
renovate | 89559557b8 | |
renovate | 84b1b61af4 | |
renovate | 7f84441e78 | |
renovate | ec6d6018da | |
renovate | bcec4b97d2 | |
renovate | 9e9e7eaecb | |
renovate | 665f1a7a18 | |
renovate | 25a56a89ae | |
renovate | 9b21d23245 | |
renovate | 5c500c711e | |
renovate | af65447920 | |
renovate | 60fee6da7f | |
renovate | d1dbc7f983 | |
renovate | 28ace38ebb | |
kolaente | 2af42f8fbe | |
kolaente | 5999def569 | |
renovate | 3b99facbfe | |
renovate | c980729b0e | |
viehlieb | b719766062 | |
kolaente | 61592a3c33 | |
renovate | e8877174d4 | |
kolaente | 2edf3aebef | |
renovate | 63b409e3bd | |
renovate | de45568a17 | |
renovate | c3224a72c1 | |
renovate | 6a151a8cf3 | |
renovate | a61ce6f1d6 | |
renovate | 0124f60bac | |
renovate | 1a14b1dac6 | |
renovate | 9662c79b95 | |
renovate | 43b67a9d33 | |
renovate | 37368a64df | |
renovate | 098113b3a4 | |
renovate | 5c13945393 | |
Dominik Pschenitschni | 86d957be4f | |
renovate | 9fe2b4ad73 | |
Dominik Pschenitschni | 72e80f637d | |
konrad | a4424e089c | |
Frederick [Bot] | 945128c3cd | |
Dominik Pschenitschni | 35cfb2f3ca | |
Dominik Pschenitschni | ccc85b9a82 | |
Dominik Pschenitschni | 9523f60763 | |
Dominik Pschenitschni | 7be8e892e2 | |
renovate | 2a6aff6ffa | |
Dominik Pschenitschni | 6a03972f16 | |
Dominik Pschenitschni | 4023ebcdd1 | |
Dominik Pschenitschni | 6049427322 | |
Dominik Pschenitschni | f658d3bbba | |
Dominik Pschenitschni | a029887102 | |
Dominik Pschenitschni | 0f7b7f72d0 | |
Dominik Pschenitschni | a29131e7d4 | |
Dominik Pschenitschni | b71d41c5ec | |
renovate | 81594e234a | |
renovate | 0846d1dc5e | |
renovate | 5dfaa48ea5 | |
renovate | 63671efbe2 | |
Dominik Pschenitschni | 4be53b098c | |
renovate | 5a89bc0183 | |
renovate | 97b1149a90 | |
renovate | 2ca33671aa | |
renovate | d82f377f94 | |
kolaente | a2cd08a7af | |
renovate | 830d6d0a38 | |
renovate | 6cc23ff7aa | |
konrad | bb4ed3d223 | |
renovate | f56302a99f | |
Dominik Pschenitschni | d850d5b98f | |
Dominik Pschenitschni | 4908469d49 | |
Dominik Pschenitschni | 6ddfba4f1f |
33
.drone.yml
33
.drone.yml
|
@ -96,7 +96,7 @@ steps:
|
||||||
- dependencies
|
- dependencies
|
||||||
|
|
||||||
- name: test-frontend
|
- name: test-frontend
|
||||||
image: cypress/browsers:node18.12.0-chrome106-ff106
|
image: cypress/browsers:node18.12.0-chrome107
|
||||||
pull: always
|
pull: always
|
||||||
environment:
|
environment:
|
||||||
CYPRESS_API_URL: http://api:3456/api/v1
|
CYPRESS_API_URL: http://api:3456/api/v1
|
||||||
|
@ -110,8 +110,7 @@ steps:
|
||||||
- sed -i 's/localhost/api/g' dist/index.html
|
- sed -i 's/localhost/api/g' dist/index.html
|
||||||
- corepack enable && pnpm config set store-dir .cache/pnpm
|
- corepack enable && pnpm config set store-dir .cache/pnpm
|
||||||
- pnpm cypress install
|
- pnpm cypress install
|
||||||
- pnpm run serve:dist & npx wait-on http://localhost:4173
|
- pnpm run test:e2e-record
|
||||||
- pnpm run test:frontend --browser chrome --record
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- build-prod
|
- build-prod
|
||||||
|
|
||||||
|
@ -150,8 +149,10 @@ steps:
|
||||||
# Override the default api url used for preview
|
# Override the default api url used for preview
|
||||||
- sed -i 's|http://localhost:3456|https://try.vikunja.io|g' dist-preview/index.html
|
- sed -i 's|http://localhost:3456|https://try.vikunja.io|g' dist-preview/index.html
|
||||||
- apk add --no-cache perl-utils
|
- apk add --no-cache perl-utils
|
||||||
- shasum -a 384 -c ./scripts/deploy-preview-netlify.js.sha384
|
# create via:
|
||||||
- node ./scripts/deploy-preview-netlify.js
|
# `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:
|
depends_on:
|
||||||
- build-prod
|
- build-prod
|
||||||
when:
|
when:
|
||||||
|
@ -204,7 +205,7 @@ steps:
|
||||||
PNPM_CACHE_FOLDER: .cache/pnpm
|
PNPM_CACHE_FOLDER: .cache/pnpm
|
||||||
commands:
|
commands:
|
||||||
- apk add git
|
- 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 install --fetch-timeout 100000 --frozen-lockfile
|
||||||
- pnpm run lint
|
- pnpm run lint
|
||||||
- "echo '{\"VERSION\": \"'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'\"}' > src/version.json"
|
- "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
|
- name: docker-unstable
|
||||||
image: thegeeklab/drone-docker-buildx
|
image: thegeeklab/drone-docker-buildx
|
||||||
privileged: true
|
privileged: true
|
||||||
pull: true
|
pull: always
|
||||||
settings:
|
settings:
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
|
@ -387,17 +388,27 @@ steps:
|
||||||
ref:
|
ref:
|
||||||
- refs/heads/main
|
- 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
|
- name: docker-release
|
||||||
image: thegeeklab/drone-docker-buildx
|
image: thegeeklab/drone-docker-buildx
|
||||||
privileged: true
|
privileged: true
|
||||||
pull: true
|
pull: always
|
||||||
settings:
|
settings:
|
||||||
username:
|
username:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
password:
|
password:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
repo: vikunja/frontend
|
repo: vikunja/frontend
|
||||||
auto_tag: true
|
|
||||||
build_args:
|
build_args:
|
||||||
- USE_RELEASE=true
|
- USE_RELEASE=true
|
||||||
- RELEASE_VERSION=${DRONE_TAG##v}
|
- RELEASE_VERSION=${DRONE_TAG##v}
|
||||||
|
@ -407,7 +418,7 @@ steps:
|
||||||
- linux/arm/v6
|
- linux/arm/v6
|
||||||
- linux/arm/v7
|
- linux/arm/v7
|
||||||
- linux/arm64/v8
|
- linux/arm64/v8
|
||||||
depends_on: [ fetch-tags ]
|
depends_on: [ generate-tags ]
|
||||||
when:
|
when:
|
||||||
ref:
|
ref:
|
||||||
- "refs/tags/**"
|
- "refs/tags/**"
|
||||||
|
@ -510,6 +521,6 @@ steps:
|
||||||
from_secret: crowdin_key
|
from_secret: crowdin_key
|
||||||
---
|
---
|
||||||
kind: signature
|
kind: signature
|
||||||
hmac: 381766491766da14ff022383ce0107955f811f7ccb55e95c9c03094e4df809e1
|
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,26 +1,31 @@
|
||||||
.DS_Store
|
# Logs
|
||||||
node_modules
|
|
||||||
/dist*
|
|
||||||
*.zip
|
|
||||||
.direnv/
|
|
||||||
|
|
||||||
# local env files
|
|
||||||
.env.local
|
|
||||||
.env.*.local
|
|
||||||
|
|
||||||
# Log files
|
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
stats.html
|
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
lerna-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
|
# Editor directories and files
|
||||||
.idea
|
|
||||||
.vscode
|
.vscode
|
||||||
|
.idea
|
||||||
*.suo
|
*.suo
|
||||||
*.ntvs*
|
*.ntvs*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
|
@ -28,9 +33,9 @@ lerna-debug.log*
|
||||||
*.sw*
|
*.sw*
|
||||||
!rollup.sw.js
|
!rollup.sw.js
|
||||||
|
|
||||||
# Test files
|
|
||||||
cypress/screenshots
|
|
||||||
cypress/videos
|
|
||||||
|
|
||||||
# Local Netlify folder
|
# Local Netlify folder
|
||||||
.netlify
|
.netlify
|
||||||
|
|
||||||
|
# histoire
|
||||||
|
.histoire
|
167
CHANGELOG.md
167
CHANGELOG.md
|
@ -9,6 +9,173 @@ 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.
|
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
|
## [0.20.2] - 2022-12-18
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
87
Dockerfile
87
Dockerfile
|
@ -1,49 +1,68 @@
|
||||||
# Stage 1: Build application
|
# syntax=docker/dockerfile:1
|
||||||
FROM --platform=$BUILDPLATFORM node:18-alpine AS compile-image
|
# ┬─┐┬ ┐o┬ ┬─┐
|
||||||
|
# │─││ │││ │ │
|
||||||
|
# ┘─┘┘─┘┘┘─┘┘─┘
|
||||||
|
|
||||||
|
FROM node:18-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
ARG USE_RELEASE=false
|
ARG USE_RELEASE=false
|
||||||
ARG RELEASE_VERSION=main
|
ARG RELEASE_VERSION=main
|
||||||
|
|
||||||
ENV PNPM_CACHE_FOLDER .cache/pnpm/
|
ENV PNPM_CACHE_FOLDER .cache/pnpm/
|
||||||
ADD . ./
|
|
||||||
|
|
||||||
RUN \
|
COPY package.json ./
|
||||||
if [ $USE_RELEASE = true ]; then \
|
COPY pnpm-lock.yaml ./
|
||||||
wget https://dl.vikunja.io/frontend/vikunja-frontend-$RELEASE_VERSION.zip -O frontend-release.zip && \
|
|
||||||
unzip frontend-release.zip -d dist/ && \
|
|
||||||
exit 0; \
|
|
||||||
fi && \
|
|
||||||
# https://pnpm.io/installation#using-corepack
|
|
||||||
corepack enable && \
|
|
||||||
# we don't use corepack prepare here by intend since
|
|
||||||
# we have renovate to keep our dependencies up to date
|
|
||||||
# Build the frontend
|
|
||||||
pnpm install && \
|
|
||||||
apk add --no-cache git && \
|
|
||||||
echo '{"VERSION": "'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'"}' > src/version.json && \
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
# Stage 2: copy
|
RUN if [ "$USE_RELEASE" != true ]; then \
|
||||||
FROM nginx:alpine
|
# https://pnpm.io/installation#using-corepack
|
||||||
|
corepack enable && \
|
||||||
|
pnpm install; \
|
||||||
|
fi
|
||||||
|
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
COPY . ./
|
||||||
COPY scripts/run.sh /run.sh
|
|
||||||
|
|
||||||
# copy compiled files from stage 1
|
RUN if [ "$USE_RELEASE" != true ]; then \
|
||||||
COPY --from=compile-image /build/dist /usr/share/nginx/html
|
apk add --no-cache --virtual .build-deps git jq && \
|
||||||
|
git describe --tags --always --abbrev=10 | sed 's/-/+/; s/^v//; s/-g/-/' | \
|
||||||
|
xargs -0 -I{} jq -Mcnr --arg version {} '{VERSION:$version}' | \
|
||||||
|
tee src/version.json && \
|
||||||
|
apk del .build-deps; \
|
||||||
|
fi
|
||||||
|
|
||||||
# Unprivileged user
|
RUN if [ "$USE_RELEASE" = true ]; then \
|
||||||
ENV PUID 1000
|
wget "https://dl.vikunja.io/frontend/vikunja-frontend-${RELEASE_VERSION}.zip" -O frontend-release.zip && \
|
||||||
ENV PGID 1000
|
unzip frontend-release.zip -d dist/; \
|
||||||
|
else \
|
||||||
|
# we don't use corepack prepare here by intend since
|
||||||
|
# we have renovate to keep our dependencies up to date
|
||||||
|
# Build the frontend
|
||||||
|
pnpm run build; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ┌┐┐┌─┐o┌┐┐┐ │
|
||||||
|
# ││││ ┬││││┌┼┘
|
||||||
|
# ┘└┘┘─┘┘┘└┘┘ └
|
||||||
|
|
||||||
|
FROM nginx:stable-alpine AS runner
|
||||||
|
WORKDIR /usr/share/nginx/html
|
||||||
LABEL maintainer="maintainers@vikunja.io"
|
LABEL maintainer="maintainers@vikunja.io"
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
ENV VIKUNJA_HTTP_PORT 80
|
||||||
# for sh file
|
ENV VIKUNJA_HTTP2_PORT 81
|
||||||
bash \
|
ENV VIKUNJA_LOG_FORMAT main
|
||||||
# installs usermod and groupmod
|
ENV VIKUNJA_API_URL http://localhost:3456/api/v1
|
||||||
shadow
|
ENV VIKUNJA_SENTRY_ENABLED false
|
||||||
|
ENV VIKUNJA_SENTRY_DSN https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480
|
||||||
|
|
||||||
CMD "/run.sh"
|
COPY docker/injector.sh /docker-entrypoint.d/50-injector.sh
|
||||||
|
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY docker/templates/. /etc/nginx/templates/
|
||||||
|
# copy compiled files from stage 1
|
||||||
|
COPY --from=builder /build/dist ./
|
||||||
|
# manage permissions
|
||||||
|
RUN chmod 0755 /docker-entrypoint.d/*.sh /etc/nginx/templates && \
|
||||||
|
chmod -R 0644 /etc/nginx/nginx.conf && \
|
||||||
|
chown -R nginx:nginx ./ /etc/nginx/conf.d /etc/nginx/templates
|
||||||
|
# unprivileged user
|
||||||
|
USER nginx
|
||||||
|
|
10
README.md
10
README.md
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/frontend/status.svg)](https://drone.kolaente.de/vikunja/frontend)
|
[![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)
|
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
|
||||||
[![Download](https://img.shields.io/badge/download-v0.20.2-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)
|
[![Translation](https://badges.crowdin.net/vikunja/localized.svg)](https://crowdin.com/project/vikunja)
|
||||||
|
|
||||||
This is the web frontend for Vikunja, written in Vue.js.
|
This is the web frontend for Vikunja, written in Vue.js.
|
||||||
|
@ -18,6 +18,14 @@ If you find any security-related issues you don't want to disclose publicly, ple
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
There is a [docker image available](https://hub.docker.com/r/vikunja/api) with support for http/2 and aggressive caching enabled.
|
There is a [docker image available](https://hub.docker.com/r/vikunja/api) with support for http/2 and aggressive caching enabled.
|
||||||
|
In order to build it from sources run the command below. (Docker >= v19.03)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
export DOCKER_BUILDKIT=1
|
||||||
|
docker build -t vikunja/frontend .
|
||||||
|
```
|
||||||
|
|
||||||
|
Refer to Refer [to multi-platform documentation](https://docs.docker.com/build/building/multi-platform/) in order to build for the different platform.
|
||||||
|
|
||||||
## Project setup
|
## Project setup
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,10 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
projectId: '181c7x',
|
projectId: '181c7x',
|
||||||
e2e: {
|
e2e: {
|
||||||
baseUrl: 'http://localhost:4173',
|
specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}',
|
||||||
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
baseUrl: 'http://127.0.0.1:4173',
|
||||||
|
experimentalRunAllSpecs: true,
|
||||||
|
// testIsolation: false,
|
||||||
},
|
},
|
||||||
component: {
|
component: {
|
||||||
devServer: {
|
devServer: {
|
||||||
|
|
|
@ -36,7 +36,7 @@ to get a shell inside the cypress container.
|
||||||
In that shell you can then execute the tests with
|
In that shell you can then execute the tests with
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm run test:frontend
|
pnpm run test:e2e
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using The Cypress Dashboard
|
### Using The Cypress Dashboard
|
||||||
|
@ -44,5 +44,5 @@ pnpm run test:frontend
|
||||||
To open the Cypress Dashboard and run tests from there, run
|
To open the Cypress Dashboard and run tests from there, run
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm run cypress:open
|
pnpm run test:e2e:dev
|
||||||
```
|
```
|
||||||
|
|
|
@ -9,7 +9,7 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- 3456:3456
|
- 3456:3456
|
||||||
cypress:
|
cypress:
|
||||||
image: cypress/browsers:node16.14.0-chrome99-ff97
|
image: cypress/browsers:node18.12.0-chrome107
|
||||||
volumes:
|
volumes:
|
||||||
- ..:/project
|
- ..:/project
|
||||||
- $HOME/.cache:/home/node/.cache/
|
- $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'
|
import {prepareLists} from './prepareLists'
|
||||||
|
|
||||||
describe('List History', () => {
|
describe('List History', () => {
|
||||||
|
createFakeUserAndLogin()
|
||||||
prepareLists()
|
prepareLists()
|
||||||
|
|
||||||
it('should show a list history on the home page', () => {
|
it('should show a list history on the home page', () => {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import {formatISO, format} from 'date-fns'
|
import {formatISO, format} from 'date-fns'
|
||||||
|
|
||||||
|
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||||
|
|
||||||
import {TaskFactory} from '../../factories/task'
|
import {TaskFactory} from '../../factories/task'
|
||||||
import {prepareLists} from './prepareLists'
|
import {prepareLists} from './prepareLists'
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
|
|
||||||
describe('List View Gantt', () => {
|
describe('List View Gantt', () => {
|
||||||
|
createFakeUserAndLogin()
|
||||||
prepareLists()
|
prepareLists()
|
||||||
|
|
||||||
it('Hides tasks with no dates', () => {
|
it('Hides tasks with no dates', () => {
|
||||||
|
@ -33,8 +35,8 @@ describe('List View Gantt', () => {
|
||||||
it('Shows tasks with dates', () => {
|
it('Shows tasks with dates', () => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const tasks = TaskFactory.create(1, {
|
const tasks = TaskFactory.create(1, {
|
||||||
start_date: formatISO(now),
|
start_date: now.toISOString(),
|
||||||
end_date: formatISO(now.setDate(now.getDate() + 4)),
|
end_date: new Date(new Date(now).setDate(now.getDate() + 4)).toISOString(),
|
||||||
})
|
})
|
||||||
cy.visit('/lists/1/gantt')
|
cy.visit('/lists/1/gantt')
|
||||||
|
|
||||||
|
@ -60,13 +62,12 @@ describe('List View Gantt', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Drags a task around', () => {
|
it('Drags a task around', () => {
|
||||||
cy.intercept('**/api/v1/tasks/*')
|
cy.intercept(Cypress.env('API_URL') + '/tasks/*').as('taskUpdate')
|
||||||
.as('taskUpdate')
|
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
TaskFactory.create(1, {
|
TaskFactory.create(1, {
|
||||||
start_date: formatISO(now),
|
start_date: now.toISOString(),
|
||||||
end_date: formatISO(now.setDate(now.getDate() + 4)),
|
end_date: new Date(new Date(now).setDate(now.getDate() + 4)).toISOString(),
|
||||||
})
|
})
|
||||||
cy.visit('/lists/1/gantt')
|
cy.visit('/lists/1/gantt')
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
|
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||||
|
|
||||||
import {BucketFactory} from '../../factories/bucket'
|
import {BucketFactory} from '../../factories/bucket'
|
||||||
import {ListFactory} from '../../factories/list'
|
import {ListFactory} from '../../factories/list'
|
||||||
import {TaskFactory} from '../../factories/task'
|
import {TaskFactory} from '../../factories/task'
|
||||||
import {prepareLists} from './prepareLists'
|
import {prepareLists} from './prepareLists'
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
|
|
||||||
describe('List View Kanban', () => {
|
describe('List View Kanban', () => {
|
||||||
let buckets
|
createFakeUserAndLogin()
|
||||||
prepareLists()
|
prepareLists()
|
||||||
|
|
||||||
|
let buckets
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
buckets = BucketFactory.create(2)
|
buckets = BucketFactory.create(2)
|
||||||
})
|
})
|
||||||
|
@ -38,7 +39,7 @@ describe('List View Kanban', () => {
|
||||||
})
|
})
|
||||||
cy.visit('/lists/1/kanban')
|
cy.visit('/lists/1/kanban')
|
||||||
|
|
||||||
cy.getSettled('.kanban .bucket')
|
cy.get('.kanban .bucket')
|
||||||
.contains(buckets[0].title)
|
.contains(buckets[0].title)
|
||||||
.get('.bucket-footer .button')
|
.get('.bucket-footer .button')
|
||||||
.contains('Add another task')
|
.contains('Add another task')
|
||||||
|
@ -70,7 +71,7 @@ describe('List View Kanban', () => {
|
||||||
it('Can set a bucket limit', () => {
|
it('Can set a bucket limit', () => {
|
||||||
cy.visit('/lists/1/kanban')
|
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()
|
.first()
|
||||||
.click()
|
.click()
|
||||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
|
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', () => {
|
it('Can rename a bucket', () => {
|
||||||
cy.visit('/lists/1/kanban')
|
cy.visit('/lists/1/kanban')
|
||||||
|
|
||||||
cy.getSettled('.kanban .bucket .bucket-header .title')
|
cy.get('.kanban .bucket .bucket-header .title')
|
||||||
.first()
|
.first()
|
||||||
.type('{selectall}New Bucket Title{enter}')
|
.type('{selectall}New Bucket Title{enter}')
|
||||||
cy.get('.kanban .bucket .bucket-header .title')
|
cy.get('.kanban .bucket .bucket-header .title')
|
||||||
|
@ -102,7 +103,7 @@ describe('List View Kanban', () => {
|
||||||
it('Can delete a bucket', () => {
|
it('Can delete a bucket', () => {
|
||||||
cy.visit('/lists/1/kanban')
|
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()
|
.first()
|
||||||
.click()
|
.click()
|
||||||
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
|
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.visit('/lists/1/kanban')
|
||||||
|
|
||||||
cy.getSettled('.kanban .bucket .tasks .task')
|
cy.get('.kanban .bucket .tasks .task')
|
||||||
.contains(tasks[0].title)
|
.contains(tasks[0].title)
|
||||||
.first()
|
.first()
|
||||||
.drag('.kanban .bucket:nth-child(2) .tasks')
|
.drag('.kanban .bucket:nth-child(2) .tasks')
|
||||||
|
@ -148,7 +149,7 @@ describe('List View Kanban', () => {
|
||||||
})
|
})
|
||||||
cy.visit('/lists/1/kanban')
|
cy.visit('/lists/1/kanban')
|
||||||
|
|
||||||
cy.getSettled('.kanban .bucket .tasks .task')
|
cy.get('.kanban .bucket .tasks .task')
|
||||||
.contains(tasks[0].title)
|
.contains(tasks[0].title)
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.click()
|
.click()
|
||||||
|
@ -170,7 +171,7 @@ describe('List View Kanban', () => {
|
||||||
const task = tasks[0]
|
const task = tasks[0]
|
||||||
cy.visit('/lists/1/kanban')
|
cy.visit('/lists/1/kanban')
|
||||||
|
|
||||||
cy.getSettled('.kanban .bucket .tasks .task')
|
cy.get('.kanban .bucket .tasks .task')
|
||||||
.contains(task.title)
|
.contains(task.title)
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.click()
|
.click()
|
||||||
|
@ -217,7 +218,7 @@ describe('List View Kanban', () => {
|
||||||
const task = tasks[0]
|
const task = tasks[0]
|
||||||
cy.visit('/lists/1/kanban')
|
cy.visit('/lists/1/kanban')
|
||||||
|
|
||||||
cy.getSettled('.kanban .bucket .tasks .task')
|
cy.get('.kanban .bucket .tasks .task')
|
||||||
.contains(task.title)
|
.contains(task.title)
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.click()
|
.click()
|
||||||
|
@ -234,7 +235,7 @@ describe('List View Kanban', () => {
|
||||||
cy.get('.global-notification')
|
cy.get('.global-notification')
|
||||||
.should('contain', 'Success')
|
.should('contain', 'Success')
|
||||||
|
|
||||||
cy.getSettled('.kanban .bucket .tasks')
|
cy.get('.kanban .bucket .tasks')
|
||||||
.should('not.contain', task.title)
|
.should('not.contain', task.title)
|
||||||
})
|
})
|
||||||
})
|
})
|
|
@ -1,12 +1,13 @@
|
||||||
|
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||||
|
|
||||||
import {UserListFactory} from '../../factories/users_list'
|
import {UserListFactory} from '../../factories/users_list'
|
||||||
import {TaskFactory} from '../../factories/task'
|
import {TaskFactory} from '../../factories/task'
|
||||||
import {UserFactory} from '../../factories/user'
|
import {UserFactory} from '../../factories/user'
|
||||||
import {ListFactory} from '../../factories/list'
|
import {ListFactory} from '../../factories/list'
|
||||||
import {prepareLists} from './prepareLists'
|
import {prepareLists} from './prepareLists'
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
|
|
||||||
describe('List View List', () => {
|
describe('List View List', () => {
|
||||||
|
createFakeUserAndLogin()
|
||||||
prepareLists()
|
prepareLists()
|
||||||
|
|
||||||
it('Should be an empty list', () => {
|
it('Should be an empty list', () => {
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||||
|
|
||||||
import {TaskFactory} from '../../factories/task'
|
import {TaskFactory} from '../../factories/task'
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
|
|
||||||
describe('List View Table', () => {
|
describe('List View Table', () => {
|
||||||
|
createFakeUserAndLogin()
|
||||||
|
|
||||||
it('Should show a table with tasks', () => {
|
it('Should show a table with tasks', () => {
|
||||||
const tasks = TaskFactory.create(1)
|
const tasks = TaskFactory.create(1)
|
||||||
cy.visit('/lists/1/table')
|
cy.visit('/lists/1/table')
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||||
|
|
||||||
import {TaskFactory} from '../../factories/task'
|
import {TaskFactory} from '../../factories/task'
|
||||||
import {prepareLists} from './prepareLists'
|
import {prepareLists} from './prepareLists'
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
|
|
||||||
describe('Lists', () => {
|
describe('Lists', () => {
|
||||||
|
createFakeUserAndLogin()
|
||||||
|
|
||||||
let lists
|
let lists
|
||||||
prepareLists((newLists) => (lists = newLists))
|
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 {ListFactory} from '../../factories/list'
|
||||||
import {NamespaceFactory} from '../../factories/namespace'
|
import {NamespaceFactory} from '../../factories/namespace'
|
||||||
|
|
||||||
describe('Namepaces', () => {
|
describe('Namepaces', () => {
|
||||||
|
createFakeUserAndLogin()
|
||||||
|
|
||||||
let namespaces
|
let namespaces
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
UserFactory.create(1)
|
|
||||||
namespaces = NamespaceFactory.create(1)
|
namespaces = NamespaceFactory.create(1)
|
||||||
ListFactory.create(1)
|
ListFactory.create(1)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import {ListFactory} from '../../factories/list'
|
import {ListFactory} from '../../factories/list'
|
||||||
import {UserFactory} from '../../factories/user'
|
|
||||||
import {NamespaceFactory} from '../../factories/namespace'
|
import {NamespaceFactory} from '../../factories/namespace'
|
||||||
import {TaskFactory} from '../../factories/task'
|
import {TaskFactory} from '../../factories/task'
|
||||||
|
|
||||||
export function createLists() {
|
export function createLists() {
|
||||||
UserFactory.create(1)
|
|
||||||
NamespaceFactory.create(1)
|
NamespaceFactory.create(1)
|
||||||
const lists = ListFactory.create(1, {
|
const lists = ListFactory.create(1, {
|
||||||
title: 'First List'
|
title: 'First List'
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
|
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||||
|
|
||||||
import {TaskFactory} from '../../factories/task'
|
import {TaskFactory} from '../../factories/task'
|
||||||
import {ListFactory} from '../../factories/list'
|
import {ListFactory} from '../../factories/list'
|
||||||
import {NamespaceFactory} from '../../factories/namespace'
|
import {NamespaceFactory} from '../../factories/namespace'
|
||||||
import {UserListFactory} from '../../factories/users_list'
|
import {UserListFactory} from '../../factories/users_list'
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
|
|
||||||
describe('Editor', () => {
|
describe('Editor', () => {
|
||||||
|
createFakeUserAndLogin()
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
NamespaceFactory.create(1)
|
NamespaceFactory.create(1)
|
||||||
const lists = ListFactory.create(1)
|
ListFactory.create(1)
|
||||||
TaskFactory.truncate()
|
TaskFactory.truncate()
|
||||||
UserListFactory.truncate()
|
UserListFactory.truncate()
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import '../../support/authenticateUser'
|
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||||
|
|
||||||
describe('The Menu', () => {
|
describe('The Menu', () => {
|
||||||
|
createFakeUserAndLogin()
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/')
|
||||||
|
})
|
||||||
|
|
||||||
it('Is visible by default on desktop', () => {
|
it('Is visible by default on desktop', () => {
|
||||||
cy.get('.namespace-container')
|
cy.get('.namespace-container')
|
||||||
.should('have.class', 'is-active')
|
.should('have.class', 'is-active')
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||||
|
|
||||||
import {TeamFactory} from '../../factories/team'
|
import {TeamFactory} from '../../factories/team'
|
||||||
import {TeamMemberFactory} from '../../factories/team_member'
|
import {TeamMemberFactory} from '../../factories/team_member'
|
||||||
import {UserFactory} from '../../factories/user'
|
import {UserFactory} from '../../factories/user'
|
||||||
import '../../support/authenticateUser'
|
|
||||||
|
|
||||||
describe('Team', () => {
|
describe('Team', () => {
|
||||||
|
createFakeUserAndLogin()
|
||||||
|
|
||||||
it('Creates a new team', () => {
|
it('Creates a new team', () => {
|
||||||
TeamFactory.truncate()
|
TeamFactory.truncate()
|
||||||
cy.visit('/teams')
|
cy.visit('/teams')
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
|
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||||
|
|
||||||
import {ListFactory} from '../../factories/list'
|
import {ListFactory} from '../../factories/list'
|
||||||
import {seed} from '../../support/seed'
|
import {seed} from '../../support/seed'
|
||||||
import {TaskFactory} from '../../factories/task'
|
import {TaskFactory} from '../../factories/task'
|
||||||
import {formatISO} from 'date-fns'
|
|
||||||
import {UserFactory} from '../../factories/user'
|
|
||||||
import {NamespaceFactory} from '../../factories/namespace'
|
import {NamespaceFactory} from '../../factories/namespace'
|
||||||
import {BucketFactory} from '../../factories/bucket'
|
import {BucketFactory} from '../../factories/bucket'
|
||||||
import {updateUserSettings} from '../../support/updateUserSettings'
|
import {updateUserSettings} from '../../support/updateUserSettings'
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
function seedTasks(numberOfTasks = 50, startDueDate = new Date()) {
|
||||||
|
|
||||||
function seedTasks(numberOfTasks = 100, startDueDate = new Date()) {
|
|
||||||
UserFactory.create(1)
|
|
||||||
NamespaceFactory.create(1)
|
NamespaceFactory.create(1)
|
||||||
const list = ListFactory.create()[0]
|
const list = ListFactory.create()[0]
|
||||||
BucketFactory.create(1, {
|
BucketFactory.create(1, {
|
||||||
|
@ -20,7 +17,7 @@ function seedTasks(numberOfTasks = 100, startDueDate = new Date()) {
|
||||||
let dueDate = startDueDate
|
let dueDate = startDueDate
|
||||||
for (let i = 0; i < numberOfTasks; i++) {
|
for (let i = 0; i < numberOfTasks; i++) {
|
||||||
const now = new Date()
|
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({
|
tasks.push({
|
||||||
id: i + 1,
|
id: i + 1,
|
||||||
list_id: list.id,
|
list_id: list.id,
|
||||||
|
@ -28,9 +25,9 @@ function seedTasks(numberOfTasks = 100, startDueDate = new Date()) {
|
||||||
created_by_id: 1,
|
created_by_id: 1,
|
||||||
title: 'Test Task ' + i,
|
title: 'Test Task ' + i,
|
||||||
index: i + 1,
|
index: i + 1,
|
||||||
due_date: formatISO(dueDate),
|
due_date: dueDate.toISOString(),
|
||||||
created: formatISO(now),
|
created: now.toISOString(),
|
||||||
updated: formatISO(now),
|
updated: now.toISOString(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
seed(TaskFactory.table, tasks)
|
seed(TaskFactory.table, tasks)
|
||||||
|
@ -38,8 +35,11 @@ function seedTasks(numberOfTasks = 100, startDueDate = new Date()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Home Page Task Overview', () => {
|
describe('Home Page Task Overview', () => {
|
||||||
|
createFakeUserAndLogin()
|
||||||
|
|
||||||
it('Should show tasks with a near due date first on the home page overview', () => {
|
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.visit('/')
|
||||||
cy.get('[data-cy="showTasks"] .card .task')
|
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', () => {
|
it('Should show overdue tasks first, then show other tasks', () => {
|
||||||
const oldDate = (new Date()).setDate((new Date()).getDate() - 14)
|
const now = new Date()
|
||||||
const {tasks} = seedTasks(100, oldDate)
|
const oldDate = new Date(new Date(now).setDate(now.getDate() - 14))
|
||||||
|
const taskCount = 50
|
||||||
|
const {tasks} = seedTasks(taskCount, oldDate)
|
||||||
|
|
||||||
cy.visit('/')
|
cy.visit('/')
|
||||||
cy.get('[data-cy="showTasks"] .card .task')
|
cy.get('[data-cy="showTasks"] .card .task')
|
||||||
|
@ -68,7 +70,7 @@ describe('Home Page Task Overview', () => {
|
||||||
TaskFactory.create(1, {
|
TaskFactory.create(1, {
|
||||||
id: 999,
|
id: 999,
|
||||||
title: newTaskTitle,
|
title: newTaskTitle,
|
||||||
due_date: formatISO(new Date()),
|
due_date: new Date().toISOString(),
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
cy.visit(`/lists/${tasks[0].list_id}/list`)
|
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', () => {
|
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
|
// 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'
|
const newTaskTitle = 'New Task'
|
||||||
|
|
||||||
cy.visit('/')
|
cy.visit('/')
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {formatISO} from 'date-fns'
|
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||||
|
|
||||||
import {TaskFactory} from '../../factories/task'
|
import {TaskFactory} from '../../factories/task'
|
||||||
import {ListFactory} from '../../factories/list'
|
import {ListFactory} from '../../factories/list'
|
||||||
|
@ -11,7 +11,6 @@ import {LabelFactory} from '../../factories/labels'
|
||||||
import {LabelTaskFactory} from '../../factories/label_task'
|
import {LabelTaskFactory} from '../../factories/label_task'
|
||||||
import {BucketFactory} from '../../factories/bucket'
|
import {BucketFactory} from '../../factories/bucket'
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
import {TaskAttachmentFactory} from '../../factories/task_attachments'
|
import {TaskAttachmentFactory} from '../../factories/task_attachments'
|
||||||
|
|
||||||
function addLabelToTaskAndVerify(labelTitle: string) {
|
function addLabelToTaskAndVerify(labelTitle: string) {
|
||||||
|
@ -46,12 +45,14 @@ function uploadAttachmentAndVerify(taskId: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Task', () => {
|
describe('Task', () => {
|
||||||
|
createFakeUserAndLogin()
|
||||||
|
|
||||||
let namespaces
|
let namespaces
|
||||||
let lists
|
let lists
|
||||||
let buckets
|
let buckets
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
UserFactory.create(1)
|
// UserFactory.create(1)
|
||||||
namespaces = NamespaceFactory.create(1)
|
namespaces = NamespaceFactory.create(1)
|
||||||
lists = ListFactory.create(1)
|
lists = ListFactory.create(1)
|
||||||
buckets = BucketFactory.create(1, {
|
buckets = BucketFactory.create(1, {
|
||||||
|
@ -145,7 +146,7 @@ describe('Task', () => {
|
||||||
id: 1,
|
id: 1,
|
||||||
index: 1,
|
index: 1,
|
||||||
done: true,
|
done: true,
|
||||||
done_at: formatISO(new Date())
|
done_at: new Date().toISOString()
|
||||||
})
|
})
|
||||||
cy.visit(`/tasks/${tasks[0].id}`)
|
cy.visit(`/tasks/${tasks[0].id}`)
|
||||||
|
|
||||||
|
@ -421,10 +422,10 @@ describe('Task', () => {
|
||||||
|
|
||||||
cy.visit(`/tasks/${tasks[0].id}`)
|
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('be.visible')
|
||||||
.should('contain', labels[0].title)
|
.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()
|
.children()
|
||||||
.first()
|
.first()
|
||||||
.get('[data-cy="taskDetail.removeLabel"]')
|
.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.')
|
cy.get('div.message.danger').contains('Wrong username or password.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const username = 'test'
|
||||||
|
|
||||||
context('Login', () => {
|
context('Login', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
UserFactory.create(1, {
|
UserFactory.create(1, {username})
|
||||||
username: 'test',
|
|
||||||
})
|
|
||||||
cy.visit('/', {
|
|
||||||
onBeforeLoad(win) {
|
|
||||||
win.localStorage.removeItem('token')
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should log in with the right credentials', () => {
|
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'
|
import {createLists} from '../list/prepareLists'
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
|
@ -10,6 +10,8 @@ function logout() {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Log out', () => {
|
describe('Log out', () => {
|
||||||
|
createFakeUserAndLogin()
|
||||||
|
|
||||||
it('Logs the user out', () => {
|
it('Logs the user out', () => {
|
||||||
cy.visit('/')
|
cy.visit('/')
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
import {UserFactory} from '../../factories/user'
|
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||||
|
|
||||||
import '../../support/authenticateUser'
|
|
||||||
|
|
||||||
describe('User Settings', () => {
|
describe('User Settings', () => {
|
||||||
beforeEach(() => {
|
createFakeUserAndLogin()
|
||||||
UserFactory.create(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Changes the user avatar', () => {
|
it('Changes the user avatar', () => {
|
||||||
cy.intercept(`${Cypress.env('API_URL')}/user/settings/avatar/upload`).as('uploadAvatar')
|
cy.intercept(`${Cypress.env('API_URL')}/user/settings/avatar/upload`).as('uploadAvatar')
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {faker} from '@faker-js/faker'
|
import {faker} from '@faker-js/faker'
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from 'date-fns'
|
|
||||||
|
|
||||||
export class BucketFactory extends Factory {
|
export class BucketFactory extends Factory {
|
||||||
static table = 'buckets'
|
static table = 'buckets'
|
||||||
|
@ -13,8 +12,8 @@ export class BucketFactory extends Factory {
|
||||||
title: faker.lorem.words(3),
|
title: faker.lorem.words(3),
|
||||||
list_id: 1,
|
list_id: 1,
|
||||||
created_by_id: 1,
|
created_by_id: 1,
|
||||||
created: formatISO(now),
|
created: now.toISOString(),
|
||||||
updated: formatISO(now)
|
updated: now.toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from 'date-fns'
|
|
||||||
|
|
||||||
export class LabelTaskFactory extends Factory {
|
export class LabelTaskFactory extends Factory {
|
||||||
static table = 'label_tasks'
|
static table = 'label_tasks'
|
||||||
|
@ -11,7 +10,7 @@ export class LabelTaskFactory extends Factory {
|
||||||
id: '{increment}',
|
id: '{increment}',
|
||||||
task_id: 1,
|
task_id: 1,
|
||||||
label_id: 1,
|
label_id: 1,
|
||||||
created: formatISO(now),
|
created: now.toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
import {faker} from '@faker-js/faker'
|
import {faker} from '@faker-js/faker'
|
||||||
|
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from 'date-fns'
|
|
||||||
|
|
||||||
export class LabelFactory extends Factory {
|
export class LabelFactory extends Factory {
|
||||||
static table = 'labels'
|
static table = 'labels'
|
||||||
|
@ -15,8 +14,8 @@ export class LabelFactory extends Factory {
|
||||||
description: faker.lorem.text(10),
|
description: faker.lorem.text(10),
|
||||||
hex_color: (Math.random()*0xFFFFFF<<0).toString(16), // random 6-digit hex number
|
hex_color: (Math.random()*0xFFFFFF<<0).toString(16), // random 6-digit hex number
|
||||||
created_by_id: 1,
|
created_by_id: 1,
|
||||||
created: formatISO(now),
|
created: now.toISOString(),
|
||||||
updated: formatISO(now),
|
updated: now.toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from "date-fns"
|
|
||||||
import {faker} from '@faker-js/faker'
|
import {faker} from '@faker-js/faker'
|
||||||
|
|
||||||
export class LinkShareFactory extends Factory {
|
export class LinkShareFactory extends Factory {
|
||||||
|
@ -15,8 +14,8 @@ export class LinkShareFactory extends Factory {
|
||||||
right: 0,
|
right: 0,
|
||||||
sharing_type: 0,
|
sharing_type: 0,
|
||||||
shared_by_id: 1,
|
shared_by_id: 1,
|
||||||
created: formatISO(now),
|
created: now.toISOString(),
|
||||||
updated: formatISO(now)
|
updated: now.toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from "date-fns"
|
|
||||||
import {faker} from '@faker-js/faker'
|
import {faker} from '@faker-js/faker'
|
||||||
|
|
||||||
export class ListFactory extends Factory {
|
export class ListFactory extends Factory {
|
||||||
|
@ -13,8 +12,8 @@ export class ListFactory extends Factory {
|
||||||
title: faker.lorem.words(3),
|
title: faker.lorem.words(3),
|
||||||
owner_id: 1,
|
owner_id: 1,
|
||||||
namespace_id: 1,
|
namespace_id: 1,
|
||||||
created: formatISO(now),
|
created: now.toISOString(),
|
||||||
updated: formatISO(now)
|
updated: now.toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
import {faker} from '@faker-js/faker'
|
import {faker} from '@faker-js/faker'
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from 'date-fns'
|
|
||||||
|
|
||||||
export class NamespaceFactory extends Factory {
|
export class NamespaceFactory extends Factory {
|
||||||
static table = 'namespaces'
|
static table = 'namespaces'
|
||||||
|
@ -12,8 +11,8 @@ export class NamespaceFactory extends Factory {
|
||||||
id: '{increment}',
|
id: '{increment}',
|
||||||
title: faker.lorem.words(3),
|
title: faker.lorem.words(3),
|
||||||
owner_id: 1,
|
owner_id: 1,
|
||||||
created: formatISO(now),
|
created: now.toISOString(),
|
||||||
updated: formatISO(now)
|
updated: now.toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {faker} from '@faker-js/faker'
|
import {faker} from '@faker-js/faker'
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from 'date-fns'
|
|
||||||
|
|
||||||
export class TaskFactory extends Factory {
|
export class TaskFactory extends Factory {
|
||||||
static table = 'tasks'
|
static table = 'tasks'
|
||||||
|
@ -16,8 +15,8 @@ export class TaskFactory extends Factory {
|
||||||
created_by_id: 1,
|
created_by_id: 1,
|
||||||
index: '{increment}',
|
index: '{increment}',
|
||||||
position: '{increment}',
|
position: '{increment}',
|
||||||
created: formatISO(now),
|
created: now.toISOString(),
|
||||||
updated: formatISO(now)
|
updated: now.toISOString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from 'date-fns'
|
|
||||||
|
|
||||||
export class TaskAssigneeFactory extends Factory {
|
export class TaskAssigneeFactory extends Factory {
|
||||||
static table = 'task_assignees'
|
static table = 'task_assignees'
|
||||||
|
@ -11,7 +10,7 @@ export class TaskAssigneeFactory extends Factory {
|
||||||
id: '{increment}',
|
id: '{increment}',
|
||||||
task_id: 1,
|
task_id: 1,
|
||||||
user_id: 1,
|
user_id: 1,
|
||||||
created: formatISO(now),
|
created: now.toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from 'date-fns'
|
|
||||||
|
|
||||||
export class TaskAttachmentFactory extends Factory {
|
export class TaskAttachmentFactory extends Factory {
|
||||||
static table = 'task_attachments'
|
static table = 'task_attachments'
|
||||||
|
@ -11,7 +10,7 @@ export class TaskAttachmentFactory extends Factory {
|
||||||
id: '{increment}',
|
id: '{increment}',
|
||||||
task_id: 1,
|
task_id: 1,
|
||||||
file_id: 1,
|
file_id: 1,
|
||||||
created: formatISO(now),
|
created: now.toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
import {faker} from '@faker-js/faker'
|
import {faker} from '@faker-js/faker'
|
||||||
|
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from "date-fns"
|
|
||||||
|
|
||||||
export class TaskCommentFactory extends Factory {
|
export class TaskCommentFactory extends Factory {
|
||||||
static table = 'task_comments'
|
static table = 'task_comments'
|
||||||
|
@ -14,8 +13,8 @@ export class TaskCommentFactory extends Factory {
|
||||||
comment: faker.lorem.text(3),
|
comment: faker.lorem.text(3),
|
||||||
author_id: 1,
|
author_id: 1,
|
||||||
task_id: 1,
|
task_id: 1,
|
||||||
created: formatISO(now),
|
created: now.toISOString(),
|
||||||
updated: formatISO(now)
|
updated: now.toISOString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {faker} from '@faker-js/faker'
|
import {faker} from '@faker-js/faker'
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from 'date-fns'
|
|
||||||
|
|
||||||
export class TeamFactory extends Factory {
|
export class TeamFactory extends Factory {
|
||||||
static table = 'teams'
|
static table = 'teams'
|
||||||
|
@ -11,8 +10,8 @@ export class TeamFactory extends Factory {
|
||||||
return {
|
return {
|
||||||
name: faker.lorem.words(3),
|
name: faker.lorem.words(3),
|
||||||
created_by_id: 1,
|
created_by_id: 1,
|
||||||
created: formatISO(now),
|
created: now.toISOString(),
|
||||||
updated: formatISO(now)
|
updated: now.toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from 'date-fns'
|
|
||||||
|
|
||||||
export class TeamMemberFactory extends Factory {
|
export class TeamMemberFactory extends Factory {
|
||||||
static table = 'team_members'
|
static table = 'team_members'
|
||||||
|
@ -9,7 +8,7 @@ export class TeamMemberFactory extends Factory {
|
||||||
team_id: 1,
|
team_id: 1,
|
||||||
user_id: 1,
|
user_id: 1,
|
||||||
admin: false,
|
admin: false,
|
||||||
created: formatISO(new Date()),
|
created: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
import {faker} from '@faker-js/faker'
|
import {faker} from '@faker-js/faker'
|
||||||
|
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from "date-fns"
|
|
||||||
|
|
||||||
export class UserFactory extends Factory {
|
export class UserFactory extends Factory {
|
||||||
static table = 'users'
|
static table = 'users'
|
||||||
|
@ -15,8 +14,8 @@ export class UserFactory extends Factory {
|
||||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.', // 1234
|
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.', // 1234
|
||||||
status: 0,
|
status: 0,
|
||||||
issuer: 'local',
|
issuer: 'local',
|
||||||
created: formatISO(now),
|
created: now.toISOString(),
|
||||||
updated: formatISO(now)
|
updated: now.toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import {Factory} from '../support/factory'
|
import {Factory} from '../support/factory'
|
||||||
import {formatISO} from "date-fns"
|
|
||||||
|
|
||||||
export class UserListFactory extends Factory {
|
export class UserListFactory extends Factory {
|
||||||
static table = 'users_lists'
|
static table = 'users_lists'
|
||||||
|
@ -12,8 +11,8 @@ export class UserListFactory extends Factory {
|
||||||
list_id: 1,
|
list_id: 1,
|
||||||
user_id: 1,
|
user_id: 1,
|
||||||
right: 0,
|
right: 0,
|
||||||
created: formatISO(now),
|
created: now.toISOString(),
|
||||||
updated: formatISO(now)
|
updated: now.toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,26 +4,32 @@
|
||||||
|
|
||||||
import {UserFactory} from '../factories/user'
|
import {UserFactory} from '../factories/user'
|
||||||
|
|
||||||
let token
|
export function login(user, cacheAcrossSpecs = false) {
|
||||||
|
if (!user) {
|
||||||
before(() => {
|
throw new Error('Needs user')
|
||||||
const users = UserFactory.create(1)
|
}
|
||||||
|
// Caching session when logging in via page visit
|
||||||
cy.request('POST', `${Cypress.env('API_URL')}/login`, {
|
cy.session(`user__${user.username}`, () => {
|
||||||
username: users[0].username,
|
cy.request('POST', `${Cypress.env('API_URL')}/login`, {
|
||||||
password: '1234',
|
username: user.username,
|
||||||
})
|
password: '1234',
|
||||||
.its('body')
|
}).then(({ body }) => {
|
||||||
.then(r => {
|
window.localStorage.setItem('token', body.token)
|
||||||
token = r.token
|
|
||||||
})
|
})
|
||||||
})
|
}, {
|
||||||
|
cacheAcrossSpecs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
export function createFakeUserAndLogin() {
|
||||||
cy.log(`Using token ${token} to make authenticated requests`)
|
let user
|
||||||
cy.visit('/', {
|
before(() => {
|
||||||
onBeforeLoad(win) {
|
user = UserFactory.create(1)[0]
|
||||||
win.localStorage.setItem('token', token)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
beforeEach(() => {
|
||||||
|
login(user, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
|
@ -34,38 +34,4 @@
|
||||||
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
|
// 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,15 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "info: API URL is $VIKUNJA_API_URL"
|
||||||
|
echo "info: Sentry enabled: $VIKUNJA_SENTRY_ENABLED"
|
||||||
|
|
||||||
|
# Escape the variable to prevent sed from complaining
|
||||||
|
VIKUNJA_API_URL="$(echo "$VIKUNJA_API_URL" | sed -r 's/([:;])/\\\1/g')"
|
||||||
|
VIKUNJA_SENTRY_DSN="$(echo "$VIKUNJA_SENTRY_DSN" | sed -r 's/([:;])/\\\1/g')"
|
||||||
|
|
||||||
|
sed -ri "s:^(\s*window.API_URL\s*=)\s*.+:\1 '${VIKUNJA_API_URL}':g" /usr/share/nginx/html/index.html
|
||||||
|
sed -ri "s:^(\s*window.SENTRY_ENABLED\s*=)\s*.+:\1 ${VIKUNJA_SENTRY_ENABLED}:g" /usr/share/nginx/html/index.html
|
||||||
|
sed -ri "s:^(\s*window.SENTRY_DSN\s*=)\s*.+:\1 '${VIKUNJA_SENTRY_DSN}':g" /usr/share/nginx/html/index.html
|
||||||
|
|
||||||
|
date -uIseconds | xargs echo 'info: started at'
|
|
@ -0,0 +1,112 @@
|
||||||
|
# Generated by nginxconfig.io
|
||||||
|
# https://www.digitalocean.com/community/tools/nginx?domains.0.server.domain=localhost&domains.0.server.documentRoot=%2Fusr%2Fshare%2Fnginx%2Fhtml&domains.0.server.cdnSubdomain=true&domains.0.https.https=false&domains.0.php.php=false&domains.0.routing.index=index.html&domains.0.routing.fallbackHtml=true&domains.0.routing.fallbackPhp=false&global.performance.assetsExpiration=1d&global.performance.mediaExpiration=1d&global.performance.svgExpiration=1d&global.performance.fontsExpiration=1d&global.logging.accessLog=%2Fdev%2Fstdout&global.logging.errorLog=%2Fdev%2Fstderr%20warn&global.logging.logNotFound=true&global.nginx.user=nginx&global.nginx.pid=%2Fvar%2Frun%2Fnginx.pid&global.nginx.clientMaxBodySize=50&global.docker.dockerfile=true&global.tools.modularizedStructure=false&global.tools.symlinkVhost=false
|
||||||
|
# and then edited manually ;)
|
||||||
|
|
||||||
|
pid /tmp/nginx.pid;
|
||||||
|
worker_processes auto;
|
||||||
|
worker_rlimit_nofile 65535;
|
||||||
|
|
||||||
|
events {
|
||||||
|
multi_accept on;
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
charset utf-8;
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
server_tokens off;
|
||||||
|
types_hash_max_size 2048;
|
||||||
|
types_hash_bucket_size 64;
|
||||||
|
|
||||||
|
# rootless
|
||||||
|
client_body_temp_path /tmp/client_temp;
|
||||||
|
proxy_temp_path /tmp/proxy_temp_path;
|
||||||
|
fastcgi_temp_path /tmp/fastcgi_temp;
|
||||||
|
uwsgi_temp_path /tmp/uwsgi_temp;
|
||||||
|
scgi_temp_path /tmp/scgi_temp;
|
||||||
|
|
||||||
|
# MIME
|
||||||
|
include mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
types {
|
||||||
|
application/manifest+json webmanifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
log_format json escape=json
|
||||||
|
'{'
|
||||||
|
'"bytes_sent": "$bytes_sent",'
|
||||||
|
'"http_user_agent": "$http_user_agent",'
|
||||||
|
'"nginx_version": "$nginx_version",'
|
||||||
|
'"query_string": "$query_string",'
|
||||||
|
'"realip_remote_addr": "$realip_remote_addr",'
|
||||||
|
'"remote_addr": "$remote_addr",'
|
||||||
|
'"remote_user": "$remote_user",'
|
||||||
|
'"request_length": "$request_length",'
|
||||||
|
'"request_method": "$request_method",'
|
||||||
|
'"request_time": "$request_time",'
|
||||||
|
'"server_addr": "$server_addr",'
|
||||||
|
'"server_port": "$server_port",'
|
||||||
|
'"server_protocol": "$server_protocol",'
|
||||||
|
'"status": "$status",'
|
||||||
|
'"time_local": "$time_local",'
|
||||||
|
'"uri": "$uri"'
|
||||||
|
'}';
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /dev/stdout main;
|
||||||
|
error_log /dev/stderr warn;
|
||||||
|
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
# compression
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_comp_level 6;
|
||||||
|
gzip_buffers 16 8k;
|
||||||
|
gzip_http_version 1.1;
|
||||||
|
gzip_min_length 256;
|
||||||
|
gzip_types
|
||||||
|
text/plain
|
||||||
|
text/css
|
||||||
|
application/json
|
||||||
|
application/x-javascript
|
||||||
|
application/javascript
|
||||||
|
text/xml
|
||||||
|
application/xml
|
||||||
|
application/xml+rss
|
||||||
|
text/javascript
|
||||||
|
application/vnd.ms-fontobject
|
||||||
|
application/x-font-ttf
|
||||||
|
font/opentype
|
||||||
|
image/svg+xml
|
||||||
|
image/x-icon
|
||||||
|
audio/wav;
|
||||||
|
|
||||||
|
map_hash_max_size 128;
|
||||||
|
map_hash_bucket_size 128;
|
||||||
|
|
||||||
|
map $sent_http_content_type $expires {
|
||||||
|
default off;
|
||||||
|
text/css max;
|
||||||
|
application/javascript max;
|
||||||
|
text/javascript max;
|
||||||
|
application/vnd.ms-fontobject max;
|
||||||
|
application/x-font-ttf max;
|
||||||
|
font/opentype max;
|
||||||
|
font/woff2 max;
|
||||||
|
image/svg+xml max;
|
||||||
|
image/x-icon max;
|
||||||
|
audio/wav max;
|
||||||
|
~images/ max;
|
||||||
|
~font/ max;
|
||||||
|
}
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
server {
|
||||||
|
listen ${VIKUNJA_HTTP_PORT};
|
||||||
|
listen [::]:${VIKUNJA_HTTP_PORT};
|
||||||
|
## Needed when behind HAProxy with SSL termination + HTTP/2 support
|
||||||
|
listen ${VIKUNJA_HTTP2_PORT} default_server http2 proxy_protocol;
|
||||||
|
listen [::]:${VIKUNJA_HTTP2_PORT} default_server http2 proxy_protocol;
|
||||||
|
|
||||||
|
server_name _;
|
||||||
|
expires $expires;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
access_log /dev/stdout ${VIKUNJA_LOG_FORMAT};
|
||||||
|
# security headers
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||||
|
add_header Content-Security-Policy "default-src 'self' http: https: ws: wss: data: blob: 'unsafe-inline'; frame-ancestors 'self';" always;
|
||||||
|
add_header Permissions-Policy "interest-cohort=()" always;
|
||||||
|
|
||||||
|
# . files
|
||||||
|
location ~ /\.(?!well-known) {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# assume that everything else is handled by the application router, by injecting the index.html.
|
||||||
|
location / {
|
||||||
|
autoindex off;
|
||||||
|
expires off;
|
||||||
|
add_header Cache-Control "public, max-age=0, s-maxage=0, must-revalidate" always;
|
||||||
|
try_files $uri /index.html =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# favicon.ico
|
||||||
|
location = /favicon.ico {
|
||||||
|
log_not_found off;
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# robots.txt
|
||||||
|
location = /robots.txt {
|
||||||
|
log_not_found off;
|
||||||
|
access_log off;
|
||||||
|
expires -1; # no-cache
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /ready {
|
||||||
|
return 200 "";
|
||||||
|
access_log off;
|
||||||
|
expires -1; # no-cache
|
||||||
|
}
|
||||||
|
|
||||||
|
# all assets contain hash in filename, cache forever
|
||||||
|
location ^~ /assets/ {
|
||||||
|
add_header Cache-Control "public, max-age=31536000, s-maxage=31536000, immutable";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# all workbox scripts are compiled with hash in filename, cache forever3
|
||||||
|
location ^~ /workbox- {
|
||||||
|
add_header Cache-Control "public, max-age=31536000, s-maxage=31536000, immutable";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# assets, media
|
||||||
|
location ~* .(txt|webmanifest|css|js|mjs|map|svg|jpg|jpeg|png|ico|ttf|woff|woff2|wav)$ {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html { }
|
||||||
|
|
||||||
|
}
|
|
@ -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/client" />
|
||||||
/// <reference types="vite-svg-loader" />
|
/// <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,9 +9,7 @@
|
||||||
|
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<link rel="apple-touch-icon" href="/images/icons/apple-touch-icon-180x180.png"/>
|
<link rel="apple-touch-icon" href="/images/icons/apple-touch-icon-180x180.png"/>
|
||||||
<link rel="preload" crossorigin="anonymous" href="/src/assets/fonts/OpenSans[wght].woff2" as="font">
|
<!--__vite-plugin-inject-preload__-->
|
||||||
<link rel="preload" crossorigin="anonymous" href="/src/assets/fonts/OpenSans-Italic[wght].woff2" as="font">
|
|
||||||
<link rel="preload" crossorigin="anonymous" href="/src/assets/fonts/Quicksand[wght].woff2" as="font">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
|
|
115
nginx.conf
115
nginx.conf
|
@ -1,115 +0,0 @@
|
||||||
user nginx;
|
|
||||||
worker_processes 1;
|
|
||||||
|
|
||||||
error_log /var/log/nginx/error.log warn;
|
|
||||||
pid /var/run/nginx.pid;
|
|
||||||
|
|
||||||
|
|
||||||
events {
|
|
||||||
worker_connections 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
http {
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
types {
|
|
||||||
application/manifest+json webmanifest;
|
|
||||||
}
|
|
||||||
|
|
||||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
|
||||||
'$status $body_bytes_sent "$http_referer" '
|
|
||||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
|
||||||
|
|
||||||
access_log /var/log/nginx/access.log main;
|
|
||||||
|
|
||||||
sendfile on;
|
|
||||||
#tcp_nopush on;
|
|
||||||
|
|
||||||
keepalive_timeout 65;
|
|
||||||
|
|
||||||
gzip on;
|
|
||||||
|
|
||||||
gzip_vary on;
|
|
||||||
gzip_proxied any;
|
|
||||||
gzip_comp_level 6;
|
|
||||||
gzip_buffers 16 8k;
|
|
||||||
gzip_http_version 1.1;
|
|
||||||
gzip_min_length 256;
|
|
||||||
gzip_types
|
|
||||||
text/plain
|
|
||||||
text/css
|
|
||||||
application/json
|
|
||||||
application/x-javascript
|
|
||||||
application/javascript
|
|
||||||
text/xml
|
|
||||||
application/xml
|
|
||||||
application/xml+rss
|
|
||||||
text/javascript
|
|
||||||
application/vnd.ms-fontobject
|
|
||||||
application/x-font-ttf
|
|
||||||
font/opentype
|
|
||||||
image/svg+xml
|
|
||||||
image/x-icon
|
|
||||||
audio/wav;
|
|
||||||
|
|
||||||
map_hash_max_size 128;
|
|
||||||
map_hash_bucket_size 128;
|
|
||||||
|
|
||||||
# Expires map
|
|
||||||
map $sent_http_content_type $expires {
|
|
||||||
default off;
|
|
||||||
text/css max;
|
|
||||||
application/javascript max;
|
|
||||||
text/javascript max;
|
|
||||||
application/vnd.ms-fontobject max;
|
|
||||||
application/x-font-ttf max;
|
|
||||||
font/opentype max;
|
|
||||||
font/woff2 max;
|
|
||||||
image/svg+xml max;
|
|
||||||
image/x-icon max;
|
|
||||||
audio/wav max;
|
|
||||||
~images/ max;
|
|
||||||
~font/ max;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
listen 81 default_server http2 proxy_protocol; ## Needed when behind HAProxy with SSL termination + HTTP/2 support
|
|
||||||
|
|
||||||
server_name _;
|
|
||||||
|
|
||||||
expires $expires;
|
|
||||||
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
|
|
||||||
# all assets contain hash in filename, cache forever
|
|
||||||
location ^~ /assets/ {
|
|
||||||
add_header Cache-Control "public, max-age=31536000, s-maxage=31536000, immutable";
|
|
||||||
try_files $uri =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
# all workbox scripts are compiled with hash in filename, cache forever3
|
|
||||||
location ^~ /workbox- {
|
|
||||||
add_header Cache-Control "public, max-age=31536000, s-maxage=31536000, immutable";
|
|
||||||
try_files $uri =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
# assume that everything else is handled by the application router, by injecting the index.html.
|
|
||||||
location / {
|
|
||||||
autoindex off;
|
|
||||||
expires off;
|
|
||||||
add_header Cache-Control "public, max-age=0, s-maxage=0, must-revalidate" always;
|
|
||||||
try_files $uri /index.html =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~* .(txt|webmanifest|css|js|mjs|map|svg|jpg|jpeg|png|ico|ttf|woff|woff2|wav)$ {
|
|
||||||
try_files $uri $uri/ =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
|
||||||
location = /50x.html {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
113
package.json
113
package.json
|
@ -1,66 +1,91 @@
|
||||||
{
|
{
|
||||||
"name": "vikunja-frontend",
|
"name": "vikunja-frontend",
|
||||||
"version": "0.10.0",
|
"description": "The todo app to organize your life.",
|
||||||
"private": true,
|
"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.2",
|
||||||
|
"keywords": [
|
||||||
|
"todo",
|
||||||
|
"productivity",
|
||||||
|
"task management",
|
||||||
|
"organisation",
|
||||||
|
"gantt",
|
||||||
|
"kanban"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vite",
|
"serve": "vite",
|
||||||
"serve:dist": "vite preview --port 4173",
|
"preview": "vite preview --port 4173",
|
||||||
"serve:dist:dev": "vite preview --mode development --port 4173",
|
"preview:dev": "vite preview --outDir dist-dev --mode development --port 4173",
|
||||||
"build": "vite build && workbox copyLibraries dist/",
|
"build": "vite build && workbox copyLibraries dist/",
|
||||||
"build:modern-only": "BUILD_MODERN_ONLY=true vite build && workbox copyLibraries dist/",
|
"build:modern-only": "BUILD_MODERN_ONLY=true vite build && workbox copyLibraries dist/",
|
||||||
"build:dev": "vite build --mode development --outDir dist-dev/",
|
"build:dev": "vite build --mode development --outDir dist-dev/",
|
||||||
"lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts",
|
"lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts",
|
||||||
"cypress:open": "cypress open",
|
"test:e2e": "start-server-and-test preview http://127.0.0.1:4173 'cypress run --e2e --browser chrome'",
|
||||||
"test:unit": "vitest --run",
|
"test:e2e-record": "start-server-and-test preview http://127.0.0.1:4173 'cypress run --e2e --browser chrome --record'",
|
||||||
"test:unit-watch": "vitest watch",
|
"test:e2e-dev-dev": "start-server-and-test preview:dev http://127.0.0.1:4173 'cypress open --e2e'",
|
||||||
"test:frontend": "cypress run",
|
"test:e2e-dev": "start-server-and-test preview http://127.0.0.1:4173 'cypress open --e2e'",
|
||||||
|
"test:unit": "vitest --dir ./src",
|
||||||
"typecheck": "vue-tsc --noEmit && vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
|
"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 run fonts:download && pnpm run fonts:subset",
|
"fonts:update": "pnpm fonts:download && pnpm fonts:subset",
|
||||||
"fonts:download": "./scripts/fonts-download.sh",
|
"fonts:download": "./scripts/fonts-download.sh",
|
||||||
"fonts:subset": "./scripts/fonts-subset.sh"
|
"fonts:subset": "./scripts/fonts-subset.sh",
|
||||||
|
"story:dev": "histoire dev",
|
||||||
|
"story:build": "histoire build",
|
||||||
|
"story:preview": "histoire preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "6.2.1",
|
"@fortawesome/fontawesome-svg-core": "6.2.1",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.2.1",
|
"@fortawesome/free-regular-svg-icons": "6.2.1",
|
||||||
"@fortawesome/free-solid-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",
|
"@github/hotkey": "2.0.1",
|
||||||
"@infectoone/vue-ganttastic": "2.1.3",
|
"@infectoone/vue-ganttastic": "2.1.4",
|
||||||
"@intlify/unplugin-vue-i18n": "0.8.1",
|
"@intlify/unplugin-vue-i18n": "0.8.1",
|
||||||
"@kyvg/vue3-notification": "2.7.0",
|
"@kyvg/vue3-notification": "2.8.0",
|
||||||
"@sentry/tracing": "7.28.1",
|
"@sentry/tracing": "7.34.0",
|
||||||
"@sentry/vue": "7.28.1",
|
"@sentry/vue": "7.34.0",
|
||||||
"@types/is-touch-device": "1.0.0",
|
"@types/is-touch-device": "1.0.0",
|
||||||
"@types/lodash.clonedeep": "4.5.7",
|
"@types/lodash.clonedeep": "4.5.7",
|
||||||
"@types/sortablejs": "1.15.0",
|
"@types/sortablejs": "1.15.0",
|
||||||
"@vueuse/core": "9.9.0",
|
"@vueuse/core": "9.12.0",
|
||||||
"axios": "0.27.2",
|
"axios": "1.2.6",
|
||||||
"blurhash": "2.0.4",
|
"blurhash": "2.0.4",
|
||||||
"bulma-css-variables": "0.9.33",
|
"bulma-css-variables": "0.9.33",
|
||||||
"camel-case": "4.1.2",
|
"camel-case": "4.1.2",
|
||||||
"codemirror": "5.65.11",
|
"codemirror": "5.65.11",
|
||||||
"date-fns": "2.29.3",
|
"date-fns": "2.29.3",
|
||||||
"dayjs": "1.11.7",
|
"dayjs": "1.11.7",
|
||||||
"dompurify": "2.4.1",
|
"dompurify": "2.4.3",
|
||||||
"easymde": "2.18.0",
|
"easymde": "2.18.0",
|
||||||
"fast-deep-equal": "3.1.3",
|
"fast-deep-equal": "3.1.3",
|
||||||
"flatpickr": "4.6.13",
|
"flatpickr": "4.6.13",
|
||||||
"flexsearch": "0.7.21",
|
"flexsearch": "0.7.21",
|
||||||
"floating-vue": "2.0.0-beta.20",
|
"floating-vue": "2.0.0-beta.20",
|
||||||
|
"focus-within": "3.0.2",
|
||||||
"highlight.js": "11.7.0",
|
"highlight.js": "11.7.0",
|
||||||
"is-touch-device": "1.0.1",
|
"is-touch-device": "1.0.1",
|
||||||
"lodash.clonedeep": "4.5.0",
|
"lodash.clonedeep": "4.5.0",
|
||||||
"lodash.debounce": "4.0.8",
|
"lodash.debounce": "4.0.8",
|
||||||
"marked": "4.2.5",
|
"marked": "4.2.12",
|
||||||
"minimist": "1.2.7",
|
"minimist": "1.2.7",
|
||||||
"pinia": "2.0.28",
|
"pinia": "2.0.29",
|
||||||
"register-service-worker": "1.7.2",
|
"register-service-worker": "1.7.2",
|
||||||
"snake-case": "3.0.4",
|
"snake-case": "3.0.4",
|
||||||
"sortablejs": "1.15.0",
|
"sortablejs": "1.15.0",
|
||||||
"ufo": "1.0.1",
|
"ufo": "1.0.1",
|
||||||
"vue": "3.2.45",
|
"vue": "3.2.45",
|
||||||
"vue-advanced-cropper": "2.8.6",
|
"vue-advanced-cropper": "2.8.8",
|
||||||
"vue-flatpickr-component": "11.0.1",
|
"vue-flatpickr-component": "11.0.1",
|
||||||
"vue-i18n": "9.2.2",
|
"vue-i18n": "9.2.2",
|
||||||
"vue-router": "4.1.6",
|
"vue-router": "4.1.6",
|
||||||
|
@ -72,45 +97,51 @@
|
||||||
"@cypress/vite-dev-server": "5.0.2",
|
"@cypress/vite-dev-server": "5.0.2",
|
||||||
"@cypress/vue": "5.0.3",
|
"@cypress/vue": "5.0.3",
|
||||||
"@faker-js/faker": "7.6.0",
|
"@faker-js/faker": "7.6.0",
|
||||||
|
"@histoire/plugin-screenshot": "0.12.4",
|
||||||
|
"@histoire/plugin-vue": "0.12.4",
|
||||||
"@rushstack/eslint-patch": "1.2.0",
|
"@rushstack/eslint-patch": "1.2.0",
|
||||||
"@types/codemirror": "5.60.6",
|
"@types/codemirror": "5.60.7",
|
||||||
"@types/dompurify": "2.4.0",
|
"@types/dompurify": "2.4.0",
|
||||||
"@types/flexsearch": "0.7.3",
|
"@types/flexsearch": "0.7.3",
|
||||||
|
"@types/focus-within": "1.0.1",
|
||||||
"@types/lodash.debounce": "4.0.7",
|
"@types/lodash.debounce": "4.0.7",
|
||||||
"@types/marked": "4.0.8",
|
"@types/marked": "4.0.8",
|
||||||
"@types/node": "18.11.18",
|
"@types/node": "18.11.18",
|
||||||
"@types/postcss-preset-env": "7.7.0",
|
"@types/postcss-preset-env": "7.7.0",
|
||||||
"@typescript-eslint/eslint-plugin": "5.48.0",
|
"@typescript-eslint/eslint-plugin": "5.49.0",
|
||||||
"@typescript-eslint/parser": "5.48.0",
|
"@typescript-eslint/parser": "5.49.0",
|
||||||
"@vitejs/plugin-legacy": "3.0.1",
|
"@vitejs/plugin-legacy": "3.0.2",
|
||||||
"@vitejs/plugin-vue": "4.0.0",
|
"@vitejs/plugin-vue": "4.0.0",
|
||||||
"@vue/eslint-config-typescript": "11.0.2",
|
"@vue/eslint-config-typescript": "11.0.2",
|
||||||
"@vue/test-utils": "2.2.6",
|
"@vue/test-utils": "2.2.8",
|
||||||
"@vue/tsconfig": "0.1.3",
|
"@vue/tsconfig": "0.1.3",
|
||||||
"autoprefixer": "10.4.13",
|
"autoprefixer": "10.4.13",
|
||||||
"browserslist": "4.21.4",
|
"browserslist": "4.21.4",
|
||||||
"caniuse-lite": "1.0.30001441",
|
"caniuse-lite": "1.0.30001449",
|
||||||
"csstype": "3.1.1",
|
"csstype": "3.1.1",
|
||||||
"cypress": "12.2.0",
|
"cypress": "12.4.1",
|
||||||
"esbuild": "0.16.13",
|
"esbuild": "0.17.5",
|
||||||
"eslint": "8.31.0",
|
"eslint": "8.33.0",
|
||||||
"eslint-plugin-vue": "9.8.0",
|
"eslint-plugin-vue": "9.9.0",
|
||||||
"happy-dom": "8.1.1",
|
"happy-dom": "8.2.0",
|
||||||
"netlify-cli": "12.5.0",
|
"histoire": "0.12.4",
|
||||||
"postcss": "8.4.20",
|
"netlify-cli": "12.10.0",
|
||||||
|
"postcss": "8.4.21",
|
||||||
|
"postcss-easing-gradients": "3.0.1",
|
||||||
|
"postcss-easings": "3.0.1",
|
||||||
"postcss-preset-env": "7.8.3",
|
"postcss-preset-env": "7.8.3",
|
||||||
"rollup": "3.9.1",
|
"rollup": "3.12.0",
|
||||||
"rollup-plugin-visualizer": "5.9.0",
|
"rollup-plugin-visualizer": "5.9.0",
|
||||||
"sass": "1.57.1",
|
"sass": "1.57.1",
|
||||||
|
"start-server-and-test": "1.15.3",
|
||||||
"typescript": "4.9.4",
|
"typescript": "4.9.4",
|
||||||
"vite": "4.0.3",
|
"vite": "4.0.4",
|
||||||
|
"vite-plugin-inject-preload": "1.2.0",
|
||||||
"vite-plugin-pwa": "0.14.1",
|
"vite-plugin-pwa": "0.14.1",
|
||||||
"vite-svg-loader": "4.0.0",
|
"vite-svg-loader": "4.0.0",
|
||||||
"vitest": "0.26.3",
|
"vitest": "0.28.3",
|
||||||
"vue-tsc": "1.0.19",
|
"vue-tsc": "1.0.24",
|
||||||
"wait-on": "7.0.1",
|
"wait-on": "7.0.1",
|
||||||
"workbox-cli": "6.5.4"
|
"workbox-cli": "6.5.4"
|
||||||
},
|
}
|
||||||
"license": "AGPL-3.0-or-later",
|
|
||||||
"packageManager": "pnpm@7.21.0"
|
|
||||||
}
|
}
|
||||||
|
|
3168
pnpm-lock.yaml
3168
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -1 +0,0 @@
|
||||||
05c69e5323a4d4bac041ade830735becd52c230277396d1f72be8fde83683a75dc095f6678804083b2ca66f27cc7995f ./scripts/deploy-preview-netlify.js
|
|
|
@ -1,14 +1,18 @@
|
||||||
const { exec } = require('child_process')
|
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 BOT_USER_ID = 513
|
||||||
const giteaToken = process.env.GITEA_TOKEN
|
const giteaToken = process.env.GITEA_TOKEN
|
||||||
const siteId = process.env.NETLIFY_SITE_ID
|
const siteId = process.env.NETLIFY_SITE_ID
|
||||||
const branchSlug = String(process.env.DRONE_SOURCE_BRANCH)
|
const branchSlug = createSlug(process.env.DRONE_SOURCE_BRANCH)
|
||||||
.trim()
|
|
||||||
.normalize('NFKD')
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/[.\s/]/g, '-')
|
|
||||||
.replace(/[^A-Za-z\d-]/g, '')
|
|
||||||
const prNumber = process.env.DRONE_PULL_REQUEST
|
const prNumber = process.env.DRONE_PULL_REQUEST
|
||||||
|
|
||||||
const prIssueCommentsUrl = `https://kolaente.dev/api/v1/repos/vikunja/frontend/issues/${prNumber}/comments`
|
const prIssueCommentsUrl = `https://kolaente.dev/api/v1/repos/vikunja/frontend/issues/${prNumber}/comments`
|
|
@ -0,0 +1 @@
|
||||||
|
57af69409e66bc87f4f2fc5822dd8d3c2eb47c601f81af1ac4a56f3e2d80837b1a2de06f4ff57695ec379b7c15b881e3 ./scripts/deploy-preview-netlify.mjs
|
|
@ -1,28 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# This shell script sets the api url based on an environment variable and starts nginx in foreground.
|
|
||||||
|
|
||||||
VIKUNJA_API_URL="${VIKUNJA_API_URL:-"/api/v1"}"
|
|
||||||
VIKUNJA_SENTRY_ENABLED="${VIKUNJA_SENTRY_ENABLED:-"false"}"
|
|
||||||
VIKUNJA_SENTRY_DSN="${VIKUNJA_SENTRY_DSN:-"https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480"}"
|
|
||||||
VIKUNJA_HTTP_PORT="${VIKUNJA_HTTP_PORT:-80}"
|
|
||||||
VIKUNJA_HTTPS_PORT="${VIKUNJA_HTTPS_PORT:-443}"
|
|
||||||
|
|
||||||
echo "Using $VIKUNJA_API_URL as default api url"
|
|
||||||
|
|
||||||
# Escape the variable to prevent sed from complaining
|
|
||||||
VIKUNJA_API_URL=$(echo $VIKUNJA_API_URL |sed 's/\//\\\//g')
|
|
||||||
|
|
||||||
sed -i "s/http\:\/\/localhost\:3456//g" /usr/share/nginx/html/index.html # replacing in two steps to make sure api urls from releases are properly replaced as well
|
|
||||||
sed -i "s/'\/api\/v1/'$VIKUNJA_API_URL/g" /usr/share/nginx/html/index.html
|
|
||||||
sed -i "s/\.SENTRY_ENABLED = false/\.SENTRY_ENABLED = $VIKUNJA_SENTRY_ENABLED/g" /usr/share/nginx/html/index.html
|
|
||||||
sed -i "s|\.SENTRY_DSN = '.*'|\.SENTRY_DSN = '$VIKUNJA_SENTRY_DSN'|g" /usr/share/nginx/html/index.html
|
|
||||||
|
|
||||||
sed -i "s/listen 80/listen $VIKUNJA_HTTP_PORT/g" /etc/nginx/nginx.conf
|
|
||||||
sed -i "s/listen 443/listen $VIKUNJA_HTTPS_PORT/g" /etc/nginx/nginx.conf
|
|
||||||
|
|
||||||
# Set the uid and gid of the nginx run user
|
|
||||||
usermod --non-unique --uid ${PUID} nginx
|
|
||||||
groupmod --non-unique --gid ${PGID} nginx
|
|
||||||
|
|
||||||
nginx -g "daemon off;"
|
|
|
@ -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 {unrefElement} from '@vueuse/core'
|
||||||
import {ref, type HTMLAttributes} from 'vue'
|
import {ref, type HTMLAttributes} from 'vue'
|
||||||
import type {RouteLocationNamedRaw} from 'vue-router'
|
import type {RouteLocationRaw} from 'vue-router'
|
||||||
|
|
||||||
export interface BaseButtonProps extends HTMLAttributes {
|
export interface BaseButtonProps extends HTMLAttributes {
|
||||||
type?: BaseButtonTypes
|
type?: BaseButtonTypes
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
to?: RouteLocationNamedRaw
|
to?: RouteLocationRaw
|
||||||
href?: string
|
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 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="content-auth">
|
<div class="content-auth">
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-if="menuActive"
|
v-show="menuActive"
|
||||||
@click="baseStore.setMenuActive(false)"
|
@click="baseStore.setMenuActive(false)"
|
||||||
class="menu-hide-button d-print-none"
|
class="menu-hide-button d-print-none"
|
||||||
>
|
>
|
||||||
<icon icon="times"/>
|
<icon icon="times"/>
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<div
|
<div
|
||||||
|
class="app-container"
|
||||||
:class="{'has-background': background || blurHash}"
|
:class="{'has-background': background || blurHash}"
|
||||||
:style="{'background-image': blurHash && `url(${blurHash})`}"
|
:style="{'background-image': blurHash && `url(${blurHash})`}"
|
||||||
class="app-container"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:class="{'is-visible': background}"
|
:class="{'is-visible': background}"
|
||||||
|
@ -18,14 +18,14 @@
|
||||||
:style="{'background-image': background && `url(${background})`}"></div>
|
:style="{'background-image': background && `url(${background})`}"></div>
|
||||||
<navigation class="d-print-none"/>
|
<navigation class="d-print-none"/>
|
||||||
<main
|
<main
|
||||||
|
class="app-content"
|
||||||
:class="[
|
:class="[
|
||||||
{ 'is-menu-enabled': menuActive },
|
{ 'is-menu-enabled': menuActive },
|
||||||
$route.name,
|
$route.name,
|
||||||
]"
|
]"
|
||||||
class="app-content"
|
|
||||||
>
|
>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
v-if="menuActive"
|
v-show="menuActive"
|
||||||
@click="baseStore.setMenuActive(false)"
|
@click="baseStore.setMenuActive(false)"
|
||||||
class="mobile-overlay d-print-none"
|
class="mobile-overlay d-print-none"
|
||||||
/>
|
/>
|
||||||
|
@ -143,7 +143,6 @@ labelStore.loadAllLabels()
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
height: 1rem;
|
|
||||||
color: var(--grey-600);
|
color: var(--grey-600);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">
|
<script setup lang="ts">
|
||||||
import {computed, ref, toRef, watch} from 'vue'
|
import {computed, ref, toRef, watch} from 'vue'
|
||||||
import {createRandomID} from '@/helpers/randomId'
|
import {createRandomID} from '@/helpers/randomId'
|
||||||
|
import XButton from '@/components/input/button.vue'
|
||||||
|
|
||||||
const DEFAULT_COLORS = [
|
const DEFAULT_COLORS = [
|
||||||
'#1973ff',
|
'#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>
|
|
@ -546,7 +546,7 @@ function select(parentIndex: number, index: number) {
|
||||||
}
|
}
|
||||||
let elems = resultRefs.value[parentIndex][index]
|
let elems = resultRefs.value[parentIndex][index]
|
||||||
if (results.value[parentIndex].items.length === 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 (
|
if (
|
||||||
typeof elems === 'undefined'
|
typeof elems === 'undefined'
|
||||||
|
@ -576,6 +576,8 @@ function reset() {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.quick-actions {
|
.quick-actions {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
// FIXME: changed position should be an option of the modal
|
// FIXME: changed position should be an option of the modal
|
||||||
:deep(.modal-content) {
|
:deep(.modal-content) {
|
||||||
top: 3rem;
|
top: 3rem;
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="task-add">
|
<div class="task-add" ref="taskAdd">
|
||||||
<div class="field is-grouped">
|
<div class="add-task__field field is-grouped">
|
||||||
<p class="control has-icons-left is-expanded">
|
<p class="control has-icons-left is-expanded">
|
||||||
<textarea
|
<textarea
|
||||||
:disabled="loading || undefined"
|
|
||||||
class="add-task-textarea input"
|
class="add-task-textarea input"
|
||||||
:class="{'textarea-empty': newTaskTitle === ''}"
|
:class="{'textarea-empty': newTaskTitle === ''}"
|
||||||
:placeholder="$t('list.list.addPlaceholder')"
|
:placeholder="$t('list.list.addPlaceholder')"
|
||||||
|
@ -33,27 +32,34 @@
|
||||||
</x-button>
|
</x-button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="help is-danger" v-if="errorMessage !== ''">
|
<Expandable :open="errorMessage !== '' || taskAddFocused || taskAddHovered && debouncedTaskAddHovered">
|
||||||
{{ errorMessage }}
|
<p class="pt-3 mt-0 help is-danger" v-if="errorMessage !== ''">
|
||||||
</p>
|
{{ errorMessage }}
|
||||||
<quick-add-magic v-else/>
|
</p>
|
||||||
|
<quick-add-magic v-else class="quick-add-magic" />
|
||||||
|
</Expandable>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed, ref} from 'vue'
|
import {computed, ref} from 'vue'
|
||||||
import {useI18n} from 'vue-i18n'
|
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 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 {parseSubtasksViaIndention} from '@/helpers/parseSubtasksViaIndention'
|
||||||
import TaskRelationService from '@/services/taskRelation'
|
import TaskRelationService from '@/services/taskRelation'
|
||||||
import TaskRelationModel from '@/models/taskRelation'
|
import TaskRelationModel from '@/models/taskRelation'
|
||||||
import {RELATION_KIND} from '@/types/IRelationKind'
|
import {getLabelsFromPrefix} from '@/modules/parseTaskText'
|
||||||
|
|
||||||
import {useAuthStore} from '@/stores/auth'
|
import {useAuthStore} from '@/stores/auth'
|
||||||
import {useTaskStore} from '@/stores/tasks'
|
import {useTaskStore} from '@/stores/tasks'
|
||||||
|
|
||||||
import {useAutoHeightTextarea} from '@/composables/useAutoHeightTextarea'
|
import {useAutoHeightTextarea} from '@/composables/useAutoHeightTextarea'
|
||||||
import {getLabelsFromPrefix} from '@/modules/parseTaskText'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
defaultPosition: {
|
defaultPosition: {
|
||||||
|
@ -71,9 +77,24 @@ const {t} = useI18n({useScope: 'global'})
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const taskStore = useTaskStore()
|
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('')
|
const errorMessage = ref('')
|
||||||
|
|
||||||
function resetEmptyTitleError(e) {
|
function resetEmptyTitleError(e: KeyboardEvent) {
|
||||||
if (
|
if (
|
||||||
(e.which <= 90 && e.which >= 48 || e.which >= 96 && e.which <= 105)
|
(e.which <= 90 && e.which >= 48 || e.which >= 96 && e.which <= 105)
|
||||||
&& newTaskTitle.value !== ''
|
&& newTaskTitle.value !== ''
|
||||||
|
@ -192,7 +213,9 @@ defineExpose({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.task-add {
|
.task-add,
|
||||||
|
// overwrite bulma styles
|
||||||
|
.task-add .add-task__field {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,4 +243,8 @@ defineExpose({
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quick-add-magic {
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||||
import Editor from '@/components/input/AsyncEditor'
|
import Editor from '@/components/input/AsyncEditor'
|
||||||
|
@ -67,7 +67,7 @@ const taskStore = useTaskStore()
|
||||||
const loading = computed(() => taskStore.isLoading)
|
const loading = computed(() => taskStore.isLoading)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
props.modelValue,
|
||||||
(value) => {
|
(value) => {
|
||||||
task.value = value
|
task.value = value
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="available">
|
<div v-if="mode !== 'disabled' && prefixes !== undefined">
|
||||||
<p class="help has-text-grey">
|
<p class="help has-text-grey">
|
||||||
{{ $t('task.quickAddMagic.hint') }}.
|
{{ $t('task.quickAddMagic.hint') }}.
|
||||||
<ButtonLink @click="() => visible = true">{{ $t('task.quickAddMagic.what') }}</ButtonLink>
|
<ButtonLink @click="() => visible = true">{{ $t('task.quickAddMagic.what') }}</ButtonLink>
|
||||||
|
@ -100,6 +100,5 @@ import {PREFIXES} from '@/modules/parseTaskText'
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const mode = ref(getQuickAddMagicMode())
|
const mode = ref(getQuickAddMagicMode())
|
||||||
|
|
||||||
const available = computed(() => mode.value !== 'disabled')
|
|
||||||
const prefixes = computed(() => PREFIXES[mode.value])
|
const prefixes = computed(() => PREFIXES[mode.value])
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ColorBubble
|
<ColorBubble
|
||||||
v-if="showListColor && listColor !== ''"
|
v-if="showListColor && listColor !== '' && currentList.id !== task.listId"
|
||||||
:color="listColor"
|
:color="listColor"
|
||||||
class="mr-1"
|
class="mr-1"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -4,11 +4,11 @@ import {debouncedWatch, tryOnMounted, useWindowSize, type MaybeRef} from '@vueus
|
||||||
// TODO: also add related styles
|
// TODO: also add related styles
|
||||||
// OR: replace with vueuse function
|
// OR: replace with vueuse function
|
||||||
export function useAutoHeightTextarea(value: MaybeRef<string>) {
|
export function useAutoHeightTextarea(value: MaybeRef<string>) {
|
||||||
const textarea = ref<HTMLInputElement>()
|
const textarea = ref<HTMLTextAreaElement | null>(null)
|
||||||
const minHeight = ref(0)
|
const minHeight = ref(0)
|
||||||
|
|
||||||
// adapted from https://github.com/LeaVerou/stretchy/blob/47f5f065c733029acccb755cae793009645809e2/src/stretchy.js#L34
|
// 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
|
if (!textareaEl) return
|
||||||
|
|
||||||
let empty
|
let empty
|
||||||
|
|
|
@ -3,12 +3,11 @@ import {useOnline as useNetworkOnline} from '@vueuse/core'
|
||||||
import type {ConfigurableWindow} from '@vueuse/core'
|
import type {ConfigurableWindow} from '@vueuse/core'
|
||||||
|
|
||||||
export function useOnline(options?: ConfigurableWindow) {
|
export function useOnline(options?: ConfigurableWindow) {
|
||||||
|
const isOnline = useNetworkOnline(options)
|
||||||
const fakeOnlineState = !!import.meta.env.VITE_IS_ONLINE
|
const fakeOnlineState = !!import.meta.env.VITE_IS_ONLINE
|
||||||
if (fakeOnlineState) {
|
if (isOnline.value === false && fakeOnlineState) {
|
||||||
console.log('Setting fake online state', fakeOnlineState)
|
console.log('Setting fake online state', fakeOnlineState)
|
||||||
|
return ref(true)
|
||||||
}
|
}
|
||||||
|
return isOnline
|
||||||
return fakeOnlineState
|
|
||||||
? ref(true)
|
|
||||||
: useNetworkOnline(options)
|
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -17,3 +17,8 @@ export const redirectToProvider = (provider: IProvider, redirectUrl = '') => {
|
||||||
|
|
||||||
window.location.href = `${provider.authUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUrl}${provider.key}&response_type=code&scope=openid email profile&state=${state}`
|
window.location.href = `${provider.authUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUrl}${provider.key}&response_type=code&scope=openid email profile&state=${state}`
|
||||||
}
|
}
|
||||||
|
export const redirectToProviderOnLogout = (provider: IProvider) => {
|
||||||
|
if (provider.logoutUrl.length > 0){
|
||||||
|
window.location.href = `${provider.logoutUrl}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,31 +14,35 @@ interface dateFoundResult {
|
||||||
|
|
||||||
const monthsRegexGroup = '(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)'
|
const monthsRegexGroup = '(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)'
|
||||||
|
|
||||||
|
function matchesDateExpr(text: string, dateExpr: string): boolean {
|
||||||
|
return text.match(new RegExp('(^| )' + dateExpr, 'g')) !== null
|
||||||
|
}
|
||||||
|
|
||||||
export const parseDate = (text: string, now: Date = new Date()): dateParseResult => {
|
export const parseDate = (text: string, now: Date = new Date()): dateParseResult => {
|
||||||
const lowerText: string = text.toLowerCase()
|
const lowerText: string = text.toLowerCase()
|
||||||
|
|
||||||
if (lowerText.includes('today')) {
|
if (matchesDateExpr(lowerText, 'today')) {
|
||||||
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('today')), 'today')
|
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('today')), 'today')
|
||||||
}
|
}
|
||||||
if (lowerText.includes('tomorrow')) {
|
if (matchesDateExpr(lowerText, 'tomorrow')) {
|
||||||
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('tomorrow')), 'tomorrow')
|
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('tomorrow')), 'tomorrow')
|
||||||
}
|
}
|
||||||
if (lowerText.includes('next monday')) {
|
if (matchesDateExpr(lowerText, 'next monday')) {
|
||||||
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('nextMonday')), 'next monday')
|
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('nextMonday')), 'next monday')
|
||||||
}
|
}
|
||||||
if (lowerText.includes('this weekend')) {
|
if (matchesDateExpr(lowerText, 'this weekend')) {
|
||||||
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('thisWeekend')), 'this weekend')
|
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('thisWeekend')), 'this weekend')
|
||||||
}
|
}
|
||||||
if (lowerText.includes('later this week')) {
|
if (matchesDateExpr(lowerText, 'later this week')) {
|
||||||
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('laterThisWeek')), 'later this week')
|
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('laterThisWeek')), 'later this week')
|
||||||
}
|
}
|
||||||
if (lowerText.includes('later next week')) {
|
if (matchesDateExpr(lowerText, 'later next week')) {
|
||||||
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('laterNextWeek')), 'later next week')
|
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('laterNextWeek')), 'later next week')
|
||||||
}
|
}
|
||||||
if (lowerText.includes('next week')) {
|
if (matchesDateExpr(lowerText, 'next week')) {
|
||||||
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('nextWeek')), 'next week')
|
return addTimeToDate(text, getDateFromInterval(calculateDayInterval('nextWeek')), 'next week')
|
||||||
}
|
}
|
||||||
if (lowerText.includes('next month')) {
|
if (matchesDateExpr(lowerText, 'next month')) {
|
||||||
const date: Date = new Date()
|
const date: Date = new Date()
|
||||||
date.setDate(1)
|
date.setDate(1)
|
||||||
date.setMonth(date.getMonth() + 1)
|
date.setMonth(date.getMonth() + 1)
|
||||||
|
@ -48,7 +52,7 @@ export const parseDate = (text: string, now: Date = new Date()): dateParseResult
|
||||||
|
|
||||||
return addTimeToDate(text, date, 'next month')
|
return addTimeToDate(text, date, 'next month')
|
||||||
}
|
}
|
||||||
if (lowerText.includes('end of month')) {
|
if (matchesDateExpr(lowerText, 'end of month')) {
|
||||||
const curDate: Date = new Date()
|
const curDate: Date = new Date()
|
||||||
const date: Date = new Date(curDate.getFullYear(), curDate.getMonth() + 1, 0)
|
const date: Date = new Date(curDate.getFullYear(), curDate.getMonth() + 1, 0)
|
||||||
date.setHours(calculateNearestHours(date))
|
date.setHours(calculateNearestHours(date))
|
||||||
|
@ -229,7 +233,7 @@ export const getDateFromTextIn = (text: string, now: Date = new Date()) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDateFromWeekday = (text: string): dateFoundResult => {
|
const getDateFromWeekday = (text: string): dateFoundResult => {
|
||||||
const matcher = / (next )?(monday|mon|tuesday|tue|wednesday|wed|thursday|thu|friday|fri|saturday|sat|sunday|sun)($| )/g
|
const matcher = /(^| )(next )?(monday|mon|tuesday|tue|wednesday|wed|thursday|thu|friday|fri|saturday|sat|sunday|sun)($| )/g
|
||||||
const results: string[] | null = matcher.exec(text.toLowerCase()) // The i modifier does not seem to work.
|
const results: string[] | null = matcher.exec(text.toLowerCase()) // The i modifier does not seem to work.
|
||||||
if (results === null) {
|
if (results === null) {
|
||||||
return {
|
return {
|
||||||
|
@ -242,7 +246,7 @@ const getDateFromWeekday = (text: string): dateFoundResult => {
|
||||||
const currentDay: number = date.getDay()
|
const currentDay: number = date.getDay()
|
||||||
let day = 0
|
let day = 0
|
||||||
|
|
||||||
switch (results[2]) {
|
switch (results[3]) {
|
||||||
case 'mon':
|
case 'mon':
|
||||||
case 'monday':
|
case 'monday':
|
||||||
day = 1
|
day = 1
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { defineSetupVue3 } from '@histoire/plugin-vue'
|
||||||
|
import {i18n} from './i18n'
|
||||||
|
|
||||||
|
// import './histoire.css' // Import global CSS
|
||||||
|
import './styles/global.scss'
|
||||||
|
|
||||||
|
import {createPinia} from 'pinia'
|
||||||
|
|
||||||
|
import FontAwesomeIcon from '@/components/misc/Icon'
|
||||||
|
import XButton from '@/components/input/button.vue'
|
||||||
|
import Modal from '@/components/misc/modal.vue'
|
||||||
|
import Card from '@/components/misc/card.vue'
|
||||||
|
|
||||||
|
|
||||||
|
export const setupVue3 = defineSetupVue3(({ app }) => {
|
||||||
|
// Add Pinia store
|
||||||
|
const pinia = createPinia()
|
||||||
|
app.use(pinia)
|
||||||
|
app.use(i18n)
|
||||||
|
|
||||||
|
app.component('icon', FontAwesomeIcon)
|
||||||
|
app.component('XButton', XButton)
|
||||||
|
app.component('modal', Modal)
|
||||||
|
app.component('card', Card)
|
||||||
|
})
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migrate from other services to Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "Import your data from {name} into Vikunja",
|
"titleService": "Import your data from {name} into Vikunja",
|
||||||
"import": "Import your data into Vikunja",
|
"import": "Import your data into Vikunja",
|
||||||
"description": "Click on the logo of one of the third-party services below to get started.",
|
"description": "Click on the logo of one of the third-party services below to get started.",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migrace z jiných služeb do Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "Importujte svá data z {name} do Vikunja",
|
"titleService": "Importujte svá data z {name} do Vikunja",
|
||||||
"import": "Importujte svá data do Vikunja",
|
"import": "Importujte svá data do Vikunja",
|
||||||
"description": "Chcete-li začít, klikněte na logo jedné ze služeb třetích stran.",
|
"description": "Chcete-li začít, klikněte na logo jedné ze služeb třetích stran.",
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Von einem anderen Dienst zu Vikunja migrieren",
|
"title": "Aus anderen Diensten importieren",
|
||||||
"titleService": "Importiere deine Daten von {name} in Vikunja",
|
"titleService": "Importiere deine Daten von {name} in Vikunja",
|
||||||
"import": "Importiere deine Daten in Vikunja",
|
"import": "Importiere deine Daten in Vikunja",
|
||||||
"description": "Klicke auf das Logo eines der unten aufgeführten Drittanbieterdienste, um loszulegen.",
|
"description": "Klicke auf das Logo eines der unten aufgeführten Drittanbieterdienste, um loszulegen.",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Vomene andere Dienst zu Vikunja migriere",
|
"title": "Aus anderen Diensten importieren",
|
||||||
"titleService": "Dini Date vo {name} in Vikunja importiere",
|
"titleService": "Dini Date vo {name} in Vikunja importiere",
|
||||||
"import": "Dini Date in Vikunja importiere",
|
"import": "Dini Date in Vikunja importiere",
|
||||||
"description": "Klick ufs Logo une vo eine vo de Drittabüüter um aahzfange.",
|
"description": "Klick ufs Logo une vo eine vo de Drittabüüter um aahzfange.",
|
||||||
|
|
|
@ -418,7 +418,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migrate from other services to Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "Import your data from {name} into Vikunja",
|
"titleService": "Import your data from {name} into Vikunja",
|
||||||
"import": "Import your data into Vikunja",
|
"import": "Import your data into Vikunja",
|
||||||
"description": "Click on the logo of one of the third-party services below to get started.",
|
"description": "Click on the logo of one of the third-party services below to get started.",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migrate from other services to Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "Import your data from {name} into Vikunja",
|
"titleService": "Import your data from {name} into Vikunja",
|
||||||
"import": "Import your data into Vikunja",
|
"import": "Import your data into Vikunja",
|
||||||
"description": "Click on the logo of one of the third-party services below to get started.",
|
"description": "Click on the logo of one of the third-party services below to get started.",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migrer d’autres services vers Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "Importe tes données depuis {name} dans Vikunja",
|
"titleService": "Importe tes données depuis {name} dans Vikunja",
|
||||||
"import": "Importer tes données dans Vikunja",
|
"import": "Importer tes données dans Vikunja",
|
||||||
"description": "Clique sur le logo d’un des services tiers ci-dessous pour commencer.",
|
"description": "Clique sur le logo d’un des services tiers ci-dessous pour commencer.",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migra da altri servizi a Vikunja",
|
"title": "Importa da altri servizi",
|
||||||
"titleService": "Importa i tuoi dati da {name} in Vikunja",
|
"titleService": "Importa i tuoi dati da {name} in Vikunja",
|
||||||
"import": "Importa i tuoi dati in Vikunja",
|
"import": "Importa i tuoi dati in Vikunja",
|
||||||
"description": "Clicca sul logo di uno dei servizi esterni qui sotto per iniziare.",
|
"description": "Clicca sul logo di uno dei servizi esterni qui sotto per iniziare.",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migreer van andere diensten naar Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "Importeer je gegevens van {name} naar Vikunja",
|
"titleService": "Importeer je gegevens van {name} naar Vikunja",
|
||||||
"import": "Importeer je data in Vikunja",
|
"import": "Importeer je data in Vikunja",
|
||||||
"description": "Click on the logo of one of the third-party services below to get started.",
|
"description": "Click on the logo of one of the third-party services below to get started.",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"home": {
|
"home": {
|
||||||
"welcomeNight": "God Morgen {username}!",
|
"welcomeNight": "God Natt {username}!",
|
||||||
"welcomeMorning": "God Morgen {username}!",
|
"welcomeMorning": "God Morgen {username}!",
|
||||||
"welcomeDay": "Hei {username}!",
|
"welcomeDay": "Hei {username}!",
|
||||||
"welcomeEvening": "God Morgen {username}!",
|
"welcomeEvening": "God Morgen {username}!",
|
||||||
|
@ -91,7 +91,7 @@
|
||||||
},
|
},
|
||||||
"totp": {
|
"totp": {
|
||||||
"title": "To-faktor-autentisering",
|
"title": "To-faktor-autentisering",
|
||||||
"enroll": "Delta",
|
"enroll": "Registrere",
|
||||||
"finishSetupPart1": "For å fullføre oppsettet, bruk denne appen (Google Authenticator eller lignende):",
|
"finishSetupPart1": "For å fullføre oppsettet, bruk denne appen (Google Authenticator eller lignende):",
|
||||||
"finishSetupPart2": "Etter det, skriv inn en kode fra appen under.",
|
"finishSetupPart2": "Etter det, skriv inn en kode fra appen under.",
|
||||||
"scanQR": "Alternativt kan du skanne denne QR-koden:",
|
"scanQR": "Alternativt kan du skanne denne QR-koden:",
|
||||||
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migrere fra andre tjenester til Vikunja",
|
"title": "Importer fra andre tjenester",
|
||||||
"titleService": "Importer dine data fra {name} til Vikunja",
|
"titleService": "Importer dine data fra {name} til Vikunja",
|
||||||
"import": "Importer dine data til Vikunja",
|
"import": "Importer dine data til Vikunja",
|
||||||
"description": "Klikk på logoen til en av tjenester fra tredjeparter nedenfor for å starte.",
|
"description": "Klikk på logoen til en av tjenester fra tredjeparter nedenfor for å starte.",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migruj z innych usług do Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "Zaimportuj swoje dane z {name} do Vikunja",
|
"titleService": "Zaimportuj swoje dane z {name} do Vikunja",
|
||||||
"import": "Zaimportuj swoje dane do Vikunja",
|
"import": "Zaimportuj swoje dane do Vikunja",
|
||||||
"description": "Aby rozpocząć, kliknij logo jednej z usług zewnętrznych.",
|
"description": "Aby rozpocząć, kliknij logo jednej z usług zewnętrznych.",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migrate from other services to Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "Import your data from {name} into Vikunja",
|
"titleService": "Import your data from {name} into Vikunja",
|
||||||
"import": "Importe seus dados para o Vikunja",
|
"import": "Importe seus dados para o Vikunja",
|
||||||
"description": "Click on the logo of one of the third-party services below to get started.",
|
"description": "Click on the logo of one of the third-party services below to get started.",
|
||||||
|
@ -538,22 +538,22 @@
|
||||||
"guide": "Guide"
|
"guide": "Guide"
|
||||||
},
|
},
|
||||||
"multiselect": {
|
"multiselect": {
|
||||||
"createPlaceholder": "Create new",
|
"createPlaceholder": "Criar novo",
|
||||||
"selectPlaceholder": "Click or press enter to select"
|
"selectPlaceholder": "Clique ou pressione Enter para selecionar"
|
||||||
},
|
},
|
||||||
"datepickerRange": {
|
"datepickerRange": {
|
||||||
"to": "To",
|
"to": "Para",
|
||||||
"from": "From",
|
"from": "De",
|
||||||
"fromto": "{from} to {to}",
|
"fromto": "{from} até {to}",
|
||||||
"ranges": {
|
"ranges": {
|
||||||
"today": "Today",
|
"today": "Hoje",
|
||||||
"thisWeek": "This Week",
|
"thisWeek": "Esta semana",
|
||||||
"restOfThisWeek": "The Rest of This Week",
|
"restOfThisWeek": "O resto desta semana",
|
||||||
"nextWeek": "Next Week",
|
"nextWeek": "Próxima semana",
|
||||||
"next7Days": "Next 7 Days",
|
"next7Days": "Próximos 7 dias",
|
||||||
"lastWeek": "Last Week",
|
"lastWeek": "Semana passada",
|
||||||
"thisMonth": "This Month",
|
"thisMonth": "Este mês",
|
||||||
"restOfThisMonth": "The Rest of This Month",
|
"restOfThisMonth": "O resto deste mês",
|
||||||
"nextMonth": "Próximo mês",
|
"nextMonth": "Próximo mês",
|
||||||
"next30Days": "Próximos 30 dias",
|
"next30Days": "Próximos 30 dias",
|
||||||
"lastMonth": "Último mês",
|
"lastMonth": "Último mês",
|
||||||
|
@ -615,7 +615,7 @@
|
||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
"chooseDueDate": "Click here to set a due date",
|
"chooseDueDate": "Click here to set a due date",
|
||||||
"chooseStartDate": "Click here to set a start date",
|
"chooseStartDate": "Clique aqui para definir uma data de início",
|
||||||
"chooseEndDate": "Click here to set an end date",
|
"chooseEndDate": "Click here to set an end date",
|
||||||
"move": "Move task to a different list",
|
"move": "Move task to a different list",
|
||||||
"done": "Marcar tarefa como concluída!",
|
"done": "Marcar tarefa como concluída!",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migrar de outros serviços para o Vikunja",
|
"title": "Importar de outros serviços",
|
||||||
"titleService": "Importar os teus dados de {name} para o Vikunja",
|
"titleService": "Importar os teus dados de {name} para o Vikunja",
|
||||||
"import": "Importar os teus dados para o Vikunja",
|
"import": "Importar os teus dados para o Vikunja",
|
||||||
"description": "Clica no logótipo de um dos serviços de terceiros abaixo para começar.",
|
"description": "Clica no logótipo de um dos serviços de terceiros abaixo para começar.",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migrate from other services to Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "Import your data from {name} into Vikunja",
|
"titleService": "Import your data from {name} into Vikunja",
|
||||||
"import": "Import your data into Vikunja",
|
"import": "Import your data into Vikunja",
|
||||||
"description": "Click on the logo of one of the third-party services below to get started.",
|
"description": "Click on the logo of one of the third-party services below to get started.",
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migrate from other services to Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "Import your data from {name} into Vikunja",
|
"titleService": "Import your data from {name} into Vikunja",
|
||||||
"import": "Import your data into Vikunja",
|
"import": "Import your data into Vikunja",
|
||||||
"description": "Click on the logo of one of the third-party services below to get started.",
|
"description": "Click on the logo of one of the third-party services below to get started.",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migrate from other services to Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "Import your data from {name} into Vikunja",
|
"titleService": "Import your data from {name} into Vikunja",
|
||||||
"import": "Import your data into Vikunja",
|
"import": "Import your data into Vikunja",
|
||||||
"description": "Click on the logo of one of the third-party services below to get started.",
|
"description": "Click on the logo of one of the third-party services below to get started.",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migrate from other services to Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "Import your data from {name} into Vikunja",
|
"titleService": "Import your data from {name} into Vikunja",
|
||||||
"import": "Import your data into Vikunja",
|
"import": "Import your data into Vikunja",
|
||||||
"description": "Click on the logo of one of the third-party services below to get started.",
|
"description": "Click on the logo of one of the third-party services below to get started.",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Di chuyển từ các dịch vụ khác đến Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "Nhập dữ liệu của bạn từ {name} vào Vikunja",
|
"titleService": "Nhập dữ liệu của bạn từ {name} vào Vikunja",
|
||||||
"import": "Nhập dữ liệu của bạn vào Vikunja",
|
"import": "Nhập dữ liệu của bạn vào Vikunja",
|
||||||
"description": "Nhấp vào Logo của một trong các dịch vụ bên dưới để bắt đầu.",
|
"description": "Nhấp vào Logo của một trong các dịch vụ bên dưới để bắt đầu.",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "从其他服务迁移到 Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "从 {name} 导入您的数据到 Vikunja",
|
"titleService": "从 {name} 导入您的数据到 Vikunja",
|
||||||
"import": "导入数据到 Vikunja",
|
"import": "导入数据到 Vikunja",
|
||||||
"description": "点击下面的第三方服务的徽标开始操作。",
|
"description": "点击下面的第三方服务的徽标开始操作。",
|
||||||
|
|
|
@ -417,7 +417,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrate": {
|
"migrate": {
|
||||||
"title": "Migrate from other services to Vikunja",
|
"title": "Import from other services",
|
||||||
"titleService": "Import your data from {name} into Vikunja",
|
"titleService": "Import your data from {name} into Vikunja",
|
||||||
"import": "Import your data into Vikunja",
|
"import": "Import your data into Vikunja",
|
||||||
"description": "Click on the logo of one of the third-party services below to get started.",
|
"description": "Click on the logo of one of the third-party services below to get started.",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue