Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
4319b4ff4a
29
.drone.yml
29
.drone.yml
|
@ -1,8 +1,7 @@
|
||||||
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
name: build
|
name: build
|
||||||
|
|
||||||
# TODO: update translations only nightly
|
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
include:
|
include:
|
||||||
|
@ -138,6 +137,26 @@ steps:
|
||||||
- failure
|
- failure
|
||||||
- success
|
- success
|
||||||
|
|
||||||
|
- name: deploy-preview
|
||||||
|
image: node:16
|
||||||
|
pull: true
|
||||||
|
environment:
|
||||||
|
NETLIFY_AUTH_TOKEN:
|
||||||
|
from_secret: netlify_auth_token
|
||||||
|
NETLIFY_SITE_ID:
|
||||||
|
from_secret: netlify_site_id
|
||||||
|
GITEA_TOKEN:
|
||||||
|
from_secret: gitea_token
|
||||||
|
commands:
|
||||||
|
- shasum -a 384 -c ./scripts/deploy-preview-netlify.js.sha384
|
||||||
|
- node ./scripts/deploy-preview-netlify.js
|
||||||
|
depends_on:
|
||||||
|
- build-prod
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
include:
|
||||||
|
- pull_request
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
name: release-latest
|
name: release-latest
|
||||||
|
@ -183,6 +202,7 @@ steps:
|
||||||
YARN_CACHE_FOLDER: .cache/yarn/
|
YARN_CACHE_FOLDER: .cache/yarn/
|
||||||
commands:
|
commands:
|
||||||
- yarn --frozen-lockfile --network-timeout 100000
|
- yarn --frozen-lockfile --network-timeout 100000
|
||||||
|
- npx browserslist@latest --update-db
|
||||||
- yarn run lint
|
- yarn 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"
|
||||||
- yarn run build
|
- yarn run build
|
||||||
|
@ -635,3 +655,8 @@ steps:
|
||||||
environment:
|
environment:
|
||||||
CROWDIN_KEY:
|
CROWDIN_KEY:
|
||||||
from_secret: crowdin_key
|
from_secret: crowdin_key
|
||||||
|
---
|
||||||
|
kind: signature
|
||||||
|
hmac: 188ee90100c5fc5922a445e531e7a47453121edddb2a64a182eb23ed2bf602de
|
||||||
|
|
||||||
|
...
|
||||||
|
|
|
@ -26,3 +26,6 @@ stats.html
|
||||||
# Test files
|
# Test files
|
||||||
cypress/screenshots
|
cypress/screenshots
|
||||||
cypress/videos
|
cypress/videos
|
||||||
|
|
||||||
|
# Local Netlify folder
|
||||||
|
.netlify
|
||||||
|
|
|
@ -9,6 +9,13 @@ 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.18.2] - 2021-11-23
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* fix(docker): properly replace api url
|
||||||
|
* fix: edit saved filter title
|
||||||
|
|
||||||
## [0.18.1] - 2021-09-08
|
## [0.18.1] - 2021-09-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -24,12 +24,6 @@ RUN \
|
||||||
# Stage 2: copy
|
# Stage 2: copy
|
||||||
FROM nginx
|
FROM nginx
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y apt-utils openssl && \
|
|
||||||
mkdir -p /etc/nginx/ssl && \
|
|
||||||
openssl genrsa -out /etc/nginx/ssl/dummy.key 2048 && \
|
|
||||||
openssl req -new -key /etc/nginx/ssl/dummy.key -out /etc/nginx/ssl/dummy.csr -subj "/C=DE/L=Berlin/O=Vikunja/CN=Vikunja Snakeoil" && \
|
|
||||||
openssl x509 -req -days 3650 -in /etc/nginx/ssl/dummy.csr -signkey /etc/nginx/ssl/dummy.key -out /etc/nginx/ssl/dummy.crt
|
|
||||||
|
|
||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
COPY run.sh /run.sh
|
COPY run.sh /run.sh
|
||||||
|
|
||||||
|
|
|
@ -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.18.1-brightgreen.svg)](https://dl.vikunja.io)
|
[![Download](https://img.shields.io/badge/download-v0.18.2-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.
|
||||||
|
|
|
@ -219,10 +219,10 @@ describe('Lists', () => {
|
||||||
cy.get('.table-view .filter-container .items .button')
|
cy.get('.table-view .filter-container .items .button')
|
||||||
.contains('Columns')
|
.contains('Columns')
|
||||||
.click()
|
.click()
|
||||||
cy.get('.table-view .filter-container .card .card-content .fancycheckbox .check')
|
cy.get('.table-view .filter-container .card.columns-filter .card-content .fancycheckbox .check')
|
||||||
.contains('Priority')
|
.contains('Priority')
|
||||||
.click()
|
.click()
|
||||||
cy.get('.table-view .filter-container .card .card-content .fancycheckbox .check')
|
cy.get('.table-view .filter-container .card.columns-filter .card-content .fancycheckbox .check')
|
||||||
.contains('Done')
|
.contains('Done')
|
||||||
.click()
|
.click()
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ describe('The Menu', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Can be hidden on desktop', () => {
|
it('Can be hidden on desktop', () => {
|
||||||
cy.get('a.menu-show-button:visible')
|
cy.get('button.menu-show-button:visible')
|
||||||
.click()
|
.click()
|
||||||
cy.get('.namespace-container')
|
cy.get('.namespace-container')
|
||||||
.should('not.have.class', 'is-active')
|
.should('not.have.class', 'is-active')
|
||||||
|
@ -21,7 +21,7 @@ describe('The Menu', () => {
|
||||||
|
|
||||||
it('Is can be shown on mobile', () => {
|
it('Is can be shown on mobile', () => {
|
||||||
cy.viewport('iphone-8')
|
cy.viewport('iphone-8')
|
||||||
cy.get('a.menu-show-button:visible')
|
cy.get('button.menu-show-button:visible')
|
||||||
.click()
|
.click()
|
||||||
cy.get('.namespace-container')
|
cy.get('.namespace-container')
|
||||||
.should('have.class', 'is-active')
|
.should('have.class', 'is-active')
|
||||||
|
|
|
@ -263,8 +263,7 @@ describe('Task', () => {
|
||||||
|
|
||||||
cy.visit(`/tasks/${tasks[0].id}`)
|
cy.visit(`/tasks/${tasks[0].id}`)
|
||||||
|
|
||||||
cy.get('.task-view .action-buttons .button')
|
cy.get('[data-cy="taskDetail.assign"]')
|
||||||
.contains('Assign this task to a user')
|
|
||||||
.click()
|
.click()
|
||||||
cy.get('.task-view .column.assignees .multiselect input')
|
cy.get('.task-view .column.assignees .multiselect input')
|
||||||
.type(users[1].username)
|
.type(users[1].username)
|
||||||
|
|
|
@ -24,8 +24,8 @@ context('Registration', () => {
|
||||||
cy.visit('/register')
|
cy.visit('/register')
|
||||||
cy.get('#username').type(fixture.username)
|
cy.get('#username').type(fixture.username)
|
||||||
cy.get('#email').type(fixture.email)
|
cy.get('#email').type(fixture.email)
|
||||||
cy.get('#password1').type(fixture.password)
|
cy.get('#password').type(fixture.password)
|
||||||
cy.get('#password2').type(fixture.password)
|
cy.get('#passwordValidation').type(fixture.password)
|
||||||
cy.get('#register-submit').click()
|
cy.get('#register-submit').click()
|
||||||
cy.url().should('include', '/')
|
cy.url().should('include', '/')
|
||||||
cy.clock(1625656161057) // 13:00
|
cy.clock(1625656161057) // 13:00
|
||||||
|
@ -42,8 +42,8 @@ context('Registration', () => {
|
||||||
cy.visit('/register')
|
cy.visit('/register')
|
||||||
cy.get('#username').type(fixture.username)
|
cy.get('#username').type(fixture.username)
|
||||||
cy.get('#email').type(fixture.email)
|
cy.get('#email').type(fixture.email)
|
||||||
cy.get('#password1').type(fixture.password)
|
cy.get('#password').type(fixture.password)
|
||||||
cy.get('#password2').type(fixture.password)
|
cy.get('#passwordValidation').type(fixture.password)
|
||||||
cy.get('#register-submit').click()
|
cy.get('#register-submit').click()
|
||||||
cy.get('div.notification.is-danger').contains('A user with this username already exists.')
|
cy.get('div.notification.is-danger').contains('A user with this username already exists.')
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
[build]
|
||||||
|
command = "yarn build"
|
||||||
|
publish = "dist"
|
||||||
|
|
||||||
|
[[redirects]]
|
||||||
|
from = "/*"
|
||||||
|
to = "/index.html"
|
||||||
|
status = 200
|
||||||
|
|
||||||
|
[[headers]]
|
||||||
|
for = "/*"
|
||||||
|
[headers.values]
|
||||||
|
X-Frame-Options = "DENY"
|
||||||
|
X-XSS-Protection = "1; mode=block"
|
||||||
|
X-Robots-Tag = "noindex"
|
11
nginx.conf
11
nginx.conf
|
@ -60,19 +60,20 @@ http {
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
listen 81 default_server http2 proxy_protocol; ## Needed when behind HAProxy with SSL termination + HTTP/2 support
|
listen 81 default_server http2 proxy_protocol; ## Needed when behind HAProxy with SSL termination + HTTP/2 support
|
||||||
listen 443 default_server ssl http2;
|
|
||||||
|
|
||||||
server_name _;
|
server_name _;
|
||||||
|
|
||||||
expires $expires;
|
expires $expires;
|
||||||
|
|
||||||
ssl_certificate /etc/nginx/ssl/dummy.crt;
|
location ~* .(txt|webmanifest|css|js|mjs|map|svg|jpg|jpeg|png|ico|ttf|woff|woff2|wav)$ {
|
||||||
ssl_certificate_key /etc/nginx/ssl/dummy.key;
|
root /usr/share/nginx/html;
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
try_files $uri $uri/ /;
|
try_files $uri $uri/ /index.html;
|
||||||
index index.html index.htm;
|
index index.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
|
62
package.json
62
package.json
|
@ -17,35 +17,39 @@
|
||||||
"browserslist:update": "npx browserslist@latest --update-db"
|
"browserslist:update": "npx browserslist@latest --update-db"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@github/hotkey": "1.6.0",
|
||||||
"@kyvg/vue3-notification": "2.3.4",
|
"@kyvg/vue3-notification": "2.3.4",
|
||||||
"@sentry/tracing": "6.14.1",
|
"@sentry/tracing": "6.15.0",
|
||||||
"@sentry/vue": "6.14.1",
|
"@sentry/vue": "6.15.0",
|
||||||
"@vue/compat": "3.2.21",
|
"@vue/compat": "3.2.22",
|
||||||
"bulma": "0.9.3",
|
"@vueuse/core": "7.0.1",
|
||||||
|
"bulma-css-variables": "0.9.33",
|
||||||
"camel-case": "4.1.2",
|
"camel-case": "4.1.2",
|
||||||
"codemirror": "5.63.3",
|
"codemirror": "5.64.0",
|
||||||
"copy-to-clipboard": "3.3.1",
|
"copy-to-clipboard": "3.3.1",
|
||||||
"date-fns": "2.25.0",
|
"date-fns": "2.26.0",
|
||||||
"dompurify": "2.3.3",
|
"dompurify": "2.3.3",
|
||||||
"easymde": "2.15.0",
|
"easymde": "2.15.0",
|
||||||
"flatpickr": "4.6.9",
|
"flatpickr": "4.6.9",
|
||||||
|
"flexsearch": "0.7.21",
|
||||||
"highlight.js": "11.3.1",
|
"highlight.js": "11.3.1",
|
||||||
"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.0.0",
|
"marked": "4.0.4",
|
||||||
"register-service-worker": "1.7.2",
|
"register-service-worker": "1.7.2",
|
||||||
"snake-case": "3.0.4",
|
"snake-case": "3.0.4",
|
||||||
"ufo": "0.7.9",
|
"ufo": "0.7.9",
|
||||||
"vue": "3.2.21",
|
"v-tooltip": "4.0.0-beta.2",
|
||||||
"vue-advanced-cropper": "2.6.3",
|
"vue": "3.2.22",
|
||||||
|
"vue-advanced-cropper": "2.7.0",
|
||||||
"vue-drag-resize": "2.0.3",
|
"vue-drag-resize": "2.0.3",
|
||||||
"vue-flatpickr-component": "9.0.5",
|
"vue-flatpickr-component": "9.0.5",
|
||||||
"vue-i18n": "9.2.0-beta.18",
|
"vue-i18n": "9.2.0-beta.20",
|
||||||
"vue-router": "4.0.12",
|
"vue-router": "4.0.12",
|
||||||
"vuedraggable": "4.1.0",
|
"vuedraggable": "4.1.0",
|
||||||
"vuex": "4.0.2",
|
"vuex": "4.0.2",
|
||||||
"workbox-precaching": "6.3.0"
|
"workbox-precaching": "6.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@4tw/cypress-drag-drop": "2.0.0",
|
"@4tw/cypress-drag-drop": "2.0.0",
|
||||||
|
@ -53,34 +57,38 @@
|
||||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||||
"@fortawesome/vue-fontawesome": "3.0.0-5",
|
"@fortawesome/vue-fontawesome": "3.0.0-5",
|
||||||
"@types/jest": "27.0.2",
|
"@types/flexsearch": "0.7.2",
|
||||||
"@typescript-eslint/eslint-plugin": "5.3.1",
|
"@types/jest": "27.0.3",
|
||||||
"@typescript-eslint/parser": "5.3.1",
|
"@typescript-eslint/eslint-plugin": "5.4.0",
|
||||||
"@vitejs/plugin-legacy": "1.6.2",
|
"@typescript-eslint/parser": "5.4.0",
|
||||||
"@vitejs/plugin-vue": "1.9.4",
|
"@vitejs/plugin-legacy": "1.6.3",
|
||||||
"@vue/eslint-config-typescript": "9.0.1",
|
"@vitejs/plugin-vue": "1.10.0",
|
||||||
"autoprefixer": "10.4.0",
|
"@vue/eslint-config-typescript": "9.1.0",
|
||||||
"axios": "0.24.0",
|
"axios": "0.24.0",
|
||||||
"browserslist": "4.17.6",
|
"browserslist": "4.18.1",
|
||||||
"cypress": "8.7.0",
|
"cypress": "8.7.0",
|
||||||
"cypress-file-upload": "5.0.8",
|
"cypress-file-upload": "5.0.8",
|
||||||
"esbuild": "0.13.13",
|
"esbuild": "0.13.15",
|
||||||
"eslint": "8.2.0",
|
"eslint": "8.3.0",
|
||||||
"eslint-plugin-vue": "8.0.3",
|
"eslint-plugin-vue": "8.1.1",
|
||||||
"express": "4.17.1",
|
"express": "4.17.1",
|
||||||
"faker": "5.5.3",
|
"faker": "5.5.3",
|
||||||
"jest": "27.3.1",
|
"jest": "27.3.1",
|
||||||
|
"netlify-cli": "7.1.0",
|
||||||
"postcss": "8.3.11",
|
"postcss": "8.3.11",
|
||||||
"rollup": "2.59.0",
|
"postcss-preset-env": "7.0.1",
|
||||||
|
"rollup": "2.60.1",
|
||||||
"rollup-plugin-visualizer": "5.5.2",
|
"rollup-plugin-visualizer": "5.5.2",
|
||||||
"sass": "1.43.4",
|
"sass": "1.43.4",
|
||||||
|
"slugify": "1.6.3",
|
||||||
"ts-jest": "27.0.7",
|
"ts-jest": "27.0.7",
|
||||||
"typescript": "4.4.4",
|
"typescript": "4.5.2",
|
||||||
"vite": "2.6.14",
|
"vite": "2.6.14",
|
||||||
"vite-plugin-pwa": "0.11.3",
|
"vite-plugin-pwa": "0.11.7",
|
||||||
"vue-tsc": "0.29.3",
|
"vite-svg-loader": "3.1.0",
|
||||||
|
"vue-tsc": "0.29.6",
|
||||||
"wait-on": "6.0.0",
|
"wait-on": "6.0.0",
|
||||||
"workbox-cli": "6.3.0"
|
"workbox-cli": "6.4.1"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
const slugify = require('slugify')
|
||||||
|
const {exec} = require('child_process')
|
||||||
|
const axios = require('axios')
|
||||||
|
|
||||||
|
const BOT_USER_ID = 513
|
||||||
|
const giteaToken = process.env.GITEA_TOKEN
|
||||||
|
const siteId = process.env.NETLIFY_SITE_ID
|
||||||
|
const branchSlug = slugify(process.env.DRONE_SOURCE_BRANCH)
|
||||||
|
const prNumber = process.env.DRONE_PULL_REQUEST
|
||||||
|
|
||||||
|
const prIssueCommentsUrl = `https://kolaente.dev/api/v1/repos/vikunja/frontend/issues/${prNumber}/comments`
|
||||||
|
const alias = `${prNumber}-${branchSlug}`
|
||||||
|
const fullPreviewUrl = `https://${alias}--vikunja-frontend-preview.netlify.app`
|
||||||
|
|
||||||
|
const promiseExec = cmd => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
exec(cmd, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(stdout)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
(async function () {
|
||||||
|
let stdout = await promiseExec(`./node_modules/.bin/netlify link --id ${siteId}`)
|
||||||
|
console.log(stdout)
|
||||||
|
stdout = await promiseExec(`./node_modules/.bin/netlify deploy --alias ${alias}`)
|
||||||
|
console.log(stdout)
|
||||||
|
|
||||||
|
const {data} = await axios.get(prIssueCommentsUrl)
|
||||||
|
const hasComment = data.some(c => c.user.id === BOT_USER_ID)
|
||||||
|
|
||||||
|
if (hasComment) {
|
||||||
|
console.log(`PR #${prNumber} already has a comment with a link, not sending another comment.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await axios.post(prIssueCommentsUrl, {
|
||||||
|
body: `
|
||||||
|
Hi ${process.env.DRONE_COMMIT_AUTHOR}!
|
||||||
|
|
||||||
|
Thank you for creating a PR!
|
||||||
|
|
||||||
|
I've deployed the changes of this PR on a preview environment under this URL: ${fullPreviewUrl}
|
||||||
|
|
||||||
|
You can use this url to view the changes live and test them out.
|
||||||
|
You will need to manually connect this to an api running somehwere. The easiest to use is https://try.vikunja.io/.
|
||||||
|
|
||||||
|
Have a nice day!
|
||||||
|
|
||||||
|
> Beep boop, I'm a bot.
|
||||||
|
`,
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'accept': 'application/json',
|
||||||
|
'Authorization': `token ${giteaToken}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`Preview comment sent successfully to PR #${prNumber}!`)
|
||||||
|
})()
|
|
@ -0,0 +1 @@
|
||||||
|
55ce0faaa2c1919341617ccfaeccbb6029ac12107964ff488985cff13dd952f1a991df3ab0d4b0705deb761e508e6434 ./scripts/deploy-preview-netlify.js
|
73
src/App.vue
73
src/App.vue
|
@ -1,25 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="{'is-touch': isTouch}">
|
<ready>
|
||||||
<div :class="{'is-hidden': !online}">
|
<div :class="{'is-touch': isTouch}">
|
||||||
<!-- This is a workaround to get the sw to "see" the to-be-cached version of the offline background image -->
|
<div :class="{'is-hidden': !online}">
|
||||||
<div class="offline" style="height: 0;width: 0;"></div>
|
<template v-if="authUser">
|
||||||
<top-navigation v-if="authUser"/>
|
<top-navigation/>
|
||||||
<content-auth v-if="authUser"/>
|
<content-auth/>
|
||||||
<content-link-share v-else-if="authLinkShare"/>
|
</template>
|
||||||
<content-no-auth v-else/>
|
<content-link-share v-else-if="authLinkShare"/>
|
||||||
<notification/>
|
<content-no-auth v-else/>
|
||||||
</div>
|
<notification/>
|
||||||
<div class="app offline" v-if="!online">
|
|
||||||
<div class="offline-message">
|
|
||||||
<h1>You are offline.</h1>
|
|
||||||
<p>Please check your network connection and try again.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<keyboard-shortcuts v-if="keyboardShortcutsActive"/>
|
<keyboard-shortcuts v-if="keyboardShortcutsActive"/>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
|
</ready>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -36,6 +32,8 @@ import ContentLinkShare from './components/home/contentLinkShare'
|
||||||
import ContentNoAuth from './components/home/contentNoAuth'
|
import ContentNoAuth from './components/home/contentNoAuth'
|
||||||
import {setLanguage} from './i18n'
|
import {setLanguage} from './i18n'
|
||||||
import AccountDeleteService from '@/services/accountDelete'
|
import AccountDeleteService from '@/services/accountDelete'
|
||||||
|
import Ready from '@/components/misc/ready'
|
||||||
|
import {useColorScheme} from '@/composables/useColorScheme'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'app',
|
name: 'app',
|
||||||
|
@ -46,6 +44,7 @@ export default defineComponent({
|
||||||
TopNavigation,
|
TopNavigation,
|
||||||
KeyboardShortcuts,
|
KeyboardShortcuts,
|
||||||
Notification,
|
Notification,
|
||||||
|
Ready,
|
||||||
},
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
this.setupOnlineStatus()
|
this.setupOnlineStatus()
|
||||||
|
@ -54,15 +53,11 @@ export default defineComponent({
|
||||||
this.setupAccountDeletionVerification()
|
this.setupAccountDeletionVerification()
|
||||||
},
|
},
|
||||||
beforeCreate() {
|
beforeCreate() {
|
||||||
// FIXME: async action in beforeCreate, might be not finished when component mounts
|
|
||||||
this.$store.dispatch('config/update')
|
|
||||||
.then(() => {
|
|
||||||
this.$store.dispatch('auth/checkAuth')
|
|
||||||
})
|
|
||||||
this.$store.dispatch('auth/checkAuth')
|
|
||||||
|
|
||||||
setLanguage()
|
setLanguage()
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
useColorScheme()
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
// Make sure to always load the home route when running with electron
|
// Make sure to always load the home route when running with electron
|
||||||
if (this.$route.fullPath.endsWith('frontend/index.html')) {
|
if (this.$route.fullPath.endsWith('frontend/index.html')) {
|
||||||
|
@ -121,29 +116,3 @@ export default defineComponent({
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '@/styles/global.scss';
|
@import '@/styles/global.scss';
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.offline {
|
|
||||||
background: url('@/assets/llama-nightscape.jpg') no-repeat center;
|
|
||||||
background-size: cover;
|
|
||||||
height: 100vh;
|
|
||||||
|
|
||||||
.offline-message {
|
|
||||||
text-align: center;
|
|
||||||
position: absolute;
|
|
||||||
width: 100vw;
|
|
||||||
bottom: 5vh;
|
|
||||||
color: $white;
|
|
||||||
padding: 0 1rem;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
text-align: center;
|
|
||||||
color: $white;
|
|
||||||
font-weight: 700 !important;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.6 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
@ -1,4 +1,4 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="256" height="256">
|
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 256 256" width="256" height="256">
|
||||||
<path d="M2268.2 2512.3a953.7 953.7 0 0 1-50 57c-180.5 189.5-426.2 294-691.6 294A953.7 953.7 0 0 1 847.8 2582a952.7 952.7 0 0 1-281.2-678.8 953.8 953.8 0 0 1 281.2-678.9 953.7 953.7 0 0 1 678.8-281.1 953.7 953.7 0 0 1 678.8 281.1 953.7 953.7 0 0 1 281.2 678.9c0 219.2-78.9 437.2-218.4 609" style="fill:#196aff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36633128" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
|
<path d="M2268.2 2512.3a953.7 953.7 0 0 1-50 57c-180.5 189.5-426.2 294-691.6 294A953.7 953.7 0 0 1 847.8 2582a952.7 952.7 0 0 1-281.2-678.8 953.8 953.8 0 0 1 281.2-678.9 953.7 953.7 0 0 1 678.8-281.1 953.7 953.7 0 0 1 678.8 281.1 953.7 953.7 0 0 1 281.2 678.9c0 219.2-78.9 437.2-218.4 609" style="fill:#196aff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36633128" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
|
||||||
<path d="M1823.7 1650.9c35.7 104.2 94.7 136.1 102 297 2.6 56.5-14.7 236-14.7 236s28 72-25.8 152.3c-83.5 124.3-255.4 132.8-345.7 132.8-90.3 0-260.2-8.5-343.7-132.8C1142 2256 1170 2184 1170 2184s-9.5-92.4-16.7-173.8c-1.7-19.1.1-94.7 2.4-113a453 453 0 0 1 25.8-96.2c14.4-39.6 36.8-79.9 54-120.5 51.8-122.8 8.4-274.9 11.1-407.3 2.2-94-20-189.3-28.7-281.2a960.4 960.4 0 0 1 308.7-50.6 958.6 958.6 0 0 1 344.9 63.6c-20.4 115-44.1 224.2-47.8 265.9-10.6 125.9-41.3 259.4 0 380" style="fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36655635" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
|
<path d="M1823.7 1650.9c35.7 104.2 94.7 136.1 102 297 2.6 56.5-14.7 236-14.7 236s28 72-25.8 152.3c-83.5 124.3-255.4 132.8-345.7 132.8-90.3 0-260.2-8.5-343.7-132.8C1142 2256 1170 2184 1170 2184s-9.5-92.4-16.7-173.8c-1.7-19.1.1-94.7 2.4-113a453 453 0 0 1 25.8-96.2c14.4-39.6 36.8-79.9 54-120.5 51.8-122.8 8.4-274.9 11.1-407.3 2.2-94-20-189.3-28.7-281.2a960.4 960.4 0 0 1 308.7-50.6 958.6 958.6 0 0 1 344.9 63.6c-20.4 115-44.1 224.2-47.8 265.9-10.6 125.9-41.3 259.4 0 380" style="fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36655635" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
|
||||||
<path d="M1162.9 2383.9c1.1-18.8 3-38 8.3-56.2 1.6-5.7 4-19.7 11.4-21.8 9-2.6 25.9 8.3 32.3 13 12.3 9 23.9 18.5 36.2 27.6 8 6 16.5 10.5 24.3 16.5 8.4 6.6 14.7 14.5 21.7 22.2 8.4 9.4 14.8 19 21.3 29.5 5.1 8.2 37.1 13.5 42.2 21 5.6 8.3 1 18.6 1 28.7 0 74.2 4.4 147.6 6.1 220.3 1.8 50 21.4 109.2-53.4 85.8-160.3-50-158.5-271.3-151.4-386.6M1869.1 2279.7c-1.6 1.8-4.2 3.2-6.3 4.8a208 208 0 0 0-25.1 21.5c-9.4 9.6-19.2 19-28.2 28.9-7.9 8.7-17.3 16.6-25 25.6-5.1 6-10 12.3-14.6 18.5-2.3 3.2-3.5 7-5.3 10.4-2.7 5-40 10.1-36.2 15 6.3 8.3 20.3 15.4 23.7 25 17.2 48.6 24.8 244.5 26.8 294.5 5.4 127.8 117.6-6.3 137.2-57.7 57-149.7 23.2-258.8-46.3-386.6" style="fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36633128" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
|
<path d="M1162.9 2383.9c1.1-18.8 3-38 8.3-56.2 1.6-5.7 4-19.7 11.4-21.8 9-2.6 25.9 8.3 32.3 13 12.3 9 23.9 18.5 36.2 27.6 8 6 16.5 10.5 24.3 16.5 8.4 6.6 14.7 14.5 21.7 22.2 8.4 9.4 14.8 19 21.3 29.5 5.1 8.2 37.1 13.5 42.2 21 5.6 8.3 1 18.6 1 28.7 0 74.2 4.4 147.6 6.1 220.3 1.8 50 21.4 109.2-53.4 85.8-160.3-50-158.5-271.3-151.4-386.6M1869.1 2279.7c-1.6 1.8-4.2 3.2-6.3 4.8a208 208 0 0 0-25.1 21.5c-9.4 9.6-19.2 19-28.2 28.9-7.9 8.7-17.3 16.6-25 25.6-5.1 6-10 12.3-14.6 18.5-2.3 3.2-3.5 7-5.3 10.4-2.7 5-40 10.1-36.2 15 6.3 8.3 20.3 15.4 23.7 25 17.2 48.6 24.8 244.5 26.8 294.5 5.4 127.8 117.6-6.3 137.2-57.7 57-149.7 23.2-258.8-46.3-386.6" style="fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36633128" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
|
||||||
|
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
@ -0,0 +1,17 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import LogoFull from '@/assets/logo-full.svg?component'
|
||||||
|
import LogoFullPride from '@/assets/logo-full-pride.svg?component'
|
||||||
|
|
||||||
|
const Logo = computed(() => new Date().getMonth() === 5 ? LogoFullPride : LogoFull)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Logo alt="Vikunja" class="logo" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.logo {
|
||||||
|
color: var(--logo-text-color);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,77 @@
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="$store.commit('toggleMenu')"
|
||||||
|
class="menu-show-button"
|
||||||
|
@shortkey="() => $store.commit('toggleMenu')"
|
||||||
|
v-shortcut="'Control+e'"
|
||||||
|
:title="$t('keyboardShortcuts.toggleMenu')"
|
||||||
|
:aria-label="menuActive ? $t('misc.hideMenu') : $t('misc.showMenu')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {computed} from 'vue'
|
||||||
|
import {store} from '@/store'
|
||||||
|
|
||||||
|
const menuActive = computed(() => store.menuActive)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$lineWidth: 2rem;
|
||||||
|
$size: $lineWidth + 1rem;
|
||||||
|
|
||||||
|
.menu-show-button {
|
||||||
|
// FIXME: create general button component
|
||||||
|
appearance: none;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
min-height: $size;
|
||||||
|
width: $size;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
$transformX: translateX(-50%);
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
height: 3px;
|
||||||
|
width: $lineWidth;
|
||||||
|
left: 50%;
|
||||||
|
transform: $transformX;
|
||||||
|
background-color: var(--grey-400);
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all $transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
top: 50%;
|
||||||
|
transform: $transformX translateY(-0.4rem)
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
bottom: 50%;
|
||||||
|
transform: $transformX translateY(0.4rem)
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
background-color: var(--grey-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
transform: $transformX translateY(-0.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
transform: $transformX translateY(0.5rem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,20 @@
|
||||||
|
<template>
|
||||||
|
<a class="menu-bottom-link" :href="poweredByUrl" target="_blank" rel="noreferrer noopener nofollow">
|
||||||
|
{{ $t('misc.poweredBy') }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {POWERED_BY as poweredByUrl} from '@/urls'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.menu-bottom-link {
|
||||||
|
color: var(--grey-300);
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
font-size: .8rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<a @click="$store.commit('menuActive', false)" class="menu-hide-button" v-if="menuActive">
|
<a @click="$store.commit('menuActive', false)" class="menu-hide-button" v-if="menuActive">
|
||||||
<icon icon="times"></icon>
|
<icon icon="times" />
|
||||||
</a>
|
</a>
|
||||||
<div
|
<div
|
||||||
:class="{'has-background': background}"
|
:class="{'has-background': background}"
|
||||||
|
@ -31,8 +31,7 @@
|
||||||
<a
|
<a
|
||||||
class="keyboard-shortcuts-button"
|
class="keyboard-shortcuts-button"
|
||||||
@click="showKeyboardShortcuts()"
|
@click="showKeyboardShortcuts()"
|
||||||
@shortkey="showKeyboardShortcuts()"
|
v-shortcut="'?'"
|
||||||
v-shortkey="['?']"
|
|
||||||
>
|
>
|
||||||
<icon icon="keyboard"/>
|
<icon icon="keyboard"/>
|
||||||
</a>
|
</a>
|
||||||
|
@ -134,6 +133,32 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.menu-hide-button {
|
||||||
|
position: fixed;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
z-index: 31;
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--grey-400);
|
||||||
|
line-height: 1;
|
||||||
|
transition: all $transition;
|
||||||
|
|
||||||
|
@media screen and (min-width: $tablet) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
height: 1rem;
|
||||||
|
color: var(--grey-600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.app-container {
|
.app-container {
|
||||||
min-height: calc(100vh - 65px);
|
min-height: calc(100vh - 65px);
|
||||||
|
|
||||||
|
@ -166,7 +191,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background: $white;
|
background: var(--white);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,7 +220,7 @@ export default {
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
z-index: 4500; // The modal has a z-index of 4000
|
z-index: 4500; // The modal has a z-index of 4000
|
||||||
|
|
||||||
color: $grey-500;
|
color: var(--grey-500);
|
||||||
transition: color $transition;
|
transition: color $transition;
|
||||||
|
|
||||||
@media screen and (max-width: $tablet) {
|
@media screen and (max-width: $tablet) {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
>
|
>
|
||||||
<div class="container has-text-centered link-share-view">
|
<div class="container has-text-centered link-share-view">
|
||||||
<div class="column is-10 is-offset-1">
|
<div class="column is-10 is-offset-1">
|
||||||
<img alt="Vikunja" class="logo" :src="logoUrl" />
|
<Logo class="logo" />
|
||||||
<h1
|
<h1
|
||||||
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
|
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
|
||||||
class="title">
|
class="title">
|
||||||
|
@ -14,9 +14,7 @@
|
||||||
</h1>
|
</h1>
|
||||||
<div class="box has-text-left view">
|
<div class="box has-text-left view">
|
||||||
<router-view/>
|
<router-view/>
|
||||||
<a class="menu-bottom-link" href="https://vikunja.io" target="_blank" rel="noreferrer noopener nofollow">
|
<PoweredByLink />
|
||||||
{{ $t('misc.poweredBy') }}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,51 +23,45 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapState} from 'vuex'
|
import {mapState} from 'vuex'
|
||||||
import {CURRENT_LIST} from '@/store/mutation-types'
|
|
||||||
|
|
||||||
import logoUrl from '@/assets/logo-full.svg'
|
import Logo from '@/components/home/Logo.vue'
|
||||||
|
import PoweredByLink from './PoweredByLink.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'contentLinkShare',
|
name: 'contentLinkShare',
|
||||||
data() {
|
components: {
|
||||||
return {
|
Logo,
|
||||||
logoUrl,
|
PoweredByLink,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: mapState({
|
computed: mapState([
|
||||||
currentList: CURRENT_LIST,
|
'currentList',
|
||||||
background: 'background',
|
'background',
|
||||||
}),
|
]),
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.link-share-container.has-background .view {
|
.link-share-container.has-background .view {
|
||||||
background: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
||||||
.logout .button {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-share-view {
|
.logo {
|
||||||
.logo {
|
max-width: 300px;
|
||||||
max-width: 300px;
|
width: 90%;
|
||||||
width: 90%;
|
margin: 2rem 0 1.5rem;
|
||||||
margin: 2rem 0 1.5rem;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.title {
|
||||||
background: $white;
|
text-shadow: 0 0 1rem var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
// FIXME: this should be defined somewhere deep
|
||||||
text-shadow: 0 0 1rem $white;
|
.link-share-view .card {
|
||||||
}
|
background-color: var(--white);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,40 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="no-auth-wrapper">
|
<no-auth-wrapper>
|
||||||
<div class="noauth-container">
|
<router-view/>
|
||||||
<img alt="Vikunja" :src="logoUrl" width="400" height="117" />
|
</no-auth-wrapper>
|
||||||
<div class="message is-info" v-if="motd !== ''">
|
|
||||||
<div class="message-header">
|
|
||||||
<p>{{ $t('misc.info') }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="message-body">
|
|
||||||
{{ motd }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<router-view/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapState} from 'vuex'
|
import {saveLastVisited} from '@/helpers/saveLastVisited'
|
||||||
|
import NoAuthWrapper from '@/components/misc/no-auth-wrapper'
|
||||||
import logoUrl from '@/assets/logo-full.svg'
|
|
||||||
import { saveLastVisited } from '@/helpers/saveLastVisited'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'contentNoAuth',
|
name: 'contentNoAuth',
|
||||||
data() {
|
components: {NoAuthWrapper},
|
||||||
return {
|
|
||||||
logoUrl,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
routeName() {
|
routeName() {
|
||||||
return this.$route.name
|
return this.$route.name
|
||||||
},
|
},
|
||||||
...mapState({
|
|
||||||
motd: state => state.config.motd,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
routeName: {
|
routeName: {
|
||||||
|
@ -65,17 +45,3 @@ export default {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.no-auth-wrapper {
|
|
||||||
background: url('@/assets/llama.svg') no-repeat bottom left fixed $light-background;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.noauth-container {
|
|
||||||
max-width: 450px;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div :class="{'is-active': menuActive}" class="namespace-container">
|
<div :class="{'is-active': menuActive}" class="namespace-container">
|
||||||
<div class="menu top-menu">
|
<div class="menu top-menu">
|
||||||
<router-link :to="{name: 'home'}" class="logo">
|
<router-link :to="{name: 'home'}" class="logo">
|
||||||
<img alt="Vikunja" :src="logoUrl" width="164" height="48"/>
|
<Logo width="164" height="48" />
|
||||||
</router-link>
|
</router-link>
|
||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
<li>
|
<li>
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="menu namespaces-lists loader-container" :class="{'is-loading': loading}">
|
<aside class="menu namespaces-lists loader-container is-loading-small" :class="{'is-loading': loading}">
|
||||||
<template v-for="(n, nk) in namespaces" :key="n.id" >
|
<template v-for="(n, nk) in namespaces" :key="n.id" >
|
||||||
<div class="namespace-title" :class="{'has-menu': n.id > 0}">
|
<div class="namespace-title" :class="{'has-menu': n.id > 0}">
|
||||||
<span
|
<span
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
>
|
>
|
||||||
<template #item="{element: l}">
|
<template #item="{element: l}">
|
||||||
<li
|
<li
|
||||||
class="loader-container"
|
class="loader-container is-loading-small"
|
||||||
:class="{'is-loading': listUpdating[l.id]}"
|
:class="{'is-loading': listUpdating[l.id]}"
|
||||||
>
|
>
|
||||||
<router-link
|
<router-link
|
||||||
|
@ -146,25 +146,34 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</aside>
|
</aside>
|
||||||
<a class="menu-bottom-link" :href="poweredByUrl" target="_blank" rel="noreferrer noopener nofollow">
|
<PoweredByLink />
|
||||||
{{ $t('misc.poweredBy') }}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapState} from 'vuex'
|
import {mapState} from 'vuex'
|
||||||
import {CURRENT_LIST, MENU_ACTIVE, LOADING, LOADING_MODULE} from '@/store/mutation-types'
|
import draggable from 'vuedraggable'
|
||||||
|
|
||||||
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
|
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
|
||||||
import NamespaceSettingsDropdown from '@/components/namespace/namespace-settings-dropdown.vue'
|
import NamespaceSettingsDropdown from '@/components/namespace/namespace-settings-dropdown.vue'
|
||||||
import draggable from 'vuedraggable'
|
import PoweredByLink from '@/components/home/PoweredByLink.vue'
|
||||||
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
|
import Logo from '@/components/home/Logo.vue'
|
||||||
import {POWERED_BY} from '@/urls'
|
|
||||||
|
import {CURRENT_LIST, MENU_ACTIVE, LOADING, LOADING_MODULE} from '@/store/mutation-types'
|
||||||
|
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
|
||||||
|
|
||||||
import logoUrl from '@/assets/logo-full.svg'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'navigation',
|
name: 'navigation',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
ListSettingsDropdown,
|
||||||
|
NamespaceSettingsDropdown,
|
||||||
|
draggable,
|
||||||
|
Logo,
|
||||||
|
PoweredByLink,
|
||||||
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
listsVisible: {},
|
listsVisible: {},
|
||||||
|
@ -174,15 +183,8 @@ export default {
|
||||||
ghostClass: 'ghost',
|
ghostClass: 'ghost',
|
||||||
},
|
},
|
||||||
listUpdating: {},
|
listUpdating: {},
|
||||||
logoUrl,
|
|
||||||
poweredByUrl: POWERED_BY,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
|
||||||
ListSettingsDropdown,
|
|
||||||
NamespaceSettingsDropdown,
|
|
||||||
draggable,
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
namespaces: state => state.namespaces.namespaces.filter(n => !n.isArchived),
|
namespaces: state => state.namespaces.namespaces.filter(n => !n.isArchived),
|
||||||
|
@ -278,8 +280,8 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$navbar-padding: 2rem;
|
$navbar-padding: 2rem;
|
||||||
$vikunja-nav-background: $light-background;
|
$vikunja-nav-background: var(--site-background);
|
||||||
$vikunja-nav-color: $grey-700;
|
$vikunja-nav-color: var(--grey-700);
|
||||||
$vikunja-nav-selected-width: 0.4rem;
|
$vikunja-nav-selected-width: 0.4rem;
|
||||||
|
|
||||||
.namespace-container {
|
.namespace-container {
|
||||||
|
@ -347,12 +349,12 @@ $vikunja-nav-selected-width: 0.4rem;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $orange;
|
color: var(--warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-favorite {
|
&.is-favorite {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
color: $orange;
|
color: var(--warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,7 +436,7 @@ $vikunja-nav-selected-width: 0.4rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $white;
|
background: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.dropdown-trigger) {
|
:deep(.dropdown-trigger) {
|
||||||
|
@ -447,14 +449,6 @@ $vikunja-nav-selected-width: 0.4rem;
|
||||||
&:hover :deep(.dropdown-trigger) {
|
&:hover :deep(.dropdown-trigger) {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.loader-container.is-loading:after {
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
top: calc(50% - .75rem);
|
|
||||||
left: calc(50% - .75rem);
|
|
||||||
border-width: 2px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.flip-list-move {
|
.flip-list-move {
|
||||||
|
@ -462,7 +456,7 @@ $vikunja-nav-selected-width: 0.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost {
|
.ghost {
|
||||||
background: $grey-200;
|
background: var(--grey-200);
|
||||||
|
|
||||||
* {
|
* {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
@ -502,25 +496,28 @@ $vikunja-nav-selected-width: 0.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.router-link-exact-active {
|
&.router-link-exact-active {
|
||||||
color: $primary;
|
color: var(--primary);
|
||||||
border-left: $vikunja-nav-selected-width solid $primary;
|
border-left: $vikunja-nav-selected-width solid var(--primary);
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: $primary;
|
color: var(--primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-left: $vikunja-nav-selected-width solid $primary;
|
border-left: $vikunja-nav-selected-width solid var(--primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
display: none;
|
display: block;
|
||||||
|
|
||||||
@media screen and (max-width: $tablet) {
|
padding-left: 2rem;
|
||||||
display: block;
|
margin-right: 1rem;
|
||||||
|
|
||||||
|
@media screen and (min-width: $tablet) {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,16 +525,8 @@ $vikunja-nav-selected-width: 0.4rem;
|
||||||
padding-top: math.div($navbar-padding, 2);
|
padding-top: math.div($navbar-padding, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.loader-container.is-loading:after {
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
top: calc(50% - .75rem);
|
|
||||||
left: calc(50% - .75rem);
|
|
||||||
border-width: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: $grey-400 !important;
|
color: var(--grey-400) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,24 +5,11 @@
|
||||||
class="navbar main-theme is-fixed-top"
|
class="navbar main-theme is-fixed-top"
|
||||||
role="navigation"
|
role="navigation"
|
||||||
>
|
>
|
||||||
<div class="navbar-brand">
|
<router-link :to="{name: 'home'}" class="logo-link">
|
||||||
<router-link :to="{name: 'home'}" class="navbar-item logo">
|
<Logo width="164" height="48"/>
|
||||||
<img width="164" height="48" alt="Vikunja" :src="logoUrl" />
|
</router-link>
|
||||||
</router-link>
|
<MenuButton class="menu-button"/>
|
||||||
<a
|
<div class="list-title" ref="listTitle" v-show="currentList.id">
|
||||||
@click="$store.commit('toggleMenu')"
|
|
||||||
class="menu-show-button"
|
|
||||||
@shortkey="() => $store.commit('toggleMenu')"
|
|
||||||
v-shortkey="['ctrl', 'e']"
|
|
||||||
>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<a
|
|
||||||
@click="$store.commit('toggleMenu')"
|
|
||||||
class="menu-show-button"
|
|
||||||
>
|
|
||||||
</a>
|
|
||||||
<div class="list-title" ref="listTitle" :style="{'display': currentList.id ? '': 'none'}">
|
|
||||||
<template v-if="currentList.id">
|
<template v-if="currentList.id">
|
||||||
<h1
|
<h1
|
||||||
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
|
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
|
||||||
|
@ -39,8 +26,8 @@
|
||||||
<a
|
<a
|
||||||
@click="openQuickActions"
|
@click="openQuickActions"
|
||||||
class="trigger-button pr-0"
|
class="trigger-button pr-0"
|
||||||
@shortkey="openQuickActions"
|
v-shortcut="'Control+k'"
|
||||||
v-shortkey="['ctrl', 'k']"
|
:title="$t('keyboardShortcuts.quickSearch')"
|
||||||
>
|
>
|
||||||
<icon icon="search"/>
|
<icon icon="search"/>
|
||||||
</a>
|
</a>
|
||||||
|
@ -101,9 +88,8 @@ import Update from '@/components/home/update.vue'
|
||||||
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
|
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
|
||||||
import Dropdown from '@/components/misc/dropdown.vue'
|
import Dropdown from '@/components/misc/dropdown.vue'
|
||||||
import Notifications from '@/components/notifications/notifications.vue'
|
import Notifications from '@/components/notifications/notifications.vue'
|
||||||
|
import Logo from '@/components/home/Logo.vue'
|
||||||
import logoUrl from '@/assets/logo-full.svg'
|
import MenuButton from '@/components/home/MenuButton.vue'
|
||||||
import logoFullPrideUrl from '@/assets/logo-full-pride.svg'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'topNavigation',
|
name: 'topNavigation',
|
||||||
|
@ -112,11 +98,10 @@ export default {
|
||||||
Dropdown,
|
Dropdown,
|
||||||
ListSettingsDropdown,
|
ListSettingsDropdown,
|
||||||
Update,
|
Update,
|
||||||
|
Logo,
|
||||||
|
MenuButton,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
logoUrl() {
|
|
||||||
return (new Date()).getMonth() === 5 ? logoFullPrideUrl : logoUrl
|
|
||||||
},
|
|
||||||
...mapState({
|
...mapState({
|
||||||
userInfo: state => state.auth.info,
|
userInfo: state => state.auth.info,
|
||||||
userAvatar: state => state.auth.avatarUrl,
|
userAvatar: state => state.auth.avatarUrl,
|
||||||
|
@ -152,27 +137,39 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$vikunja-nav-logo-full-width: 164px;
|
$vikunja-nav-logo-full-width: 164px;
|
||||||
|
$user-dropdown-width-mobile: 5rem;
|
||||||
|
|
||||||
|
$hamburger-menu-icon-spacing: 1rem;
|
||||||
|
$hamburger-menu-icon-width: 28px;
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
z-index: 4 !important;
|
z-index: 4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.navbar-brand {
|
.logo-link {
|
||||||
|
display: none;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
|
||||||
|
@media screen and (min-width: $tablet) {
|
||||||
|
align-self: stretch;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding-left: 2rem;
|
||||||
.logo img {
|
margin-right: 1.5rem;
|
||||||
width: $vikunja-nav-logo-full-width;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&.is-dark .navbar-brand > .navbar-item {
|
}
|
||||||
@media screen and (max-width: $tablet) {
|
|
||||||
margin: 0 auto;
|
.menu-button {
|
||||||
}
|
align-self: stretch;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
@media screen and (max-width: $tablet) {
|
||||||
|
margin-left: $hamburger-menu-icon-spacing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar.main-theme {
|
.navbar.main-theme {
|
||||||
background: $light-background;
|
background: var(--site-background);
|
||||||
z-index: 5 !important;
|
z-index: 5 !important;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -197,10 +194,6 @@ $vikunja-nav-logo-full-width: 164px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $tablet) {
|
@media screen and (max-width: $tablet) {
|
||||||
.navbar-brand {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user {
|
.user {
|
||||||
width: $user-dropdown-width-mobile;
|
width: $user-dropdown-width-mobile;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -231,7 +224,7 @@ $vikunja-nav-logo-full-width: 164px;
|
||||||
:deep() {
|
:deep() {
|
||||||
.trigger-button {
|
.trigger-button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: $grey-400;
|
color: var(--grey-400);
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -268,33 +261,33 @@ $vikunja-nav-logo-full-width: 164px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-title {
|
.list-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
$edit-icon-width: 1rem;
|
$edit-icon-width: 1rem;
|
||||||
|
|
||||||
@media screen and (min-width: $tablet) {
|
@media screen and (min-width: $tablet) {
|
||||||
// We need a fixed width for overflowing ellipsis to work
|
// We need a fixed width for overflowing ellipsis to work
|
||||||
--nav-username-width: 0;
|
--nav-username-width: 0;
|
||||||
width: calc(100vw - #{$user-dropdown-width-mobile} - #{2 * $hamburger-menu-icon-spacing} - #{$hamburger-menu-icon-width} - #{$edit-icon-width} - #{2 * $navbar-icon-width} - #{$vikunja-nav-logo-full-width} - var(--nav-username-width));
|
width: calc(100vw - #{$user-dropdown-width-mobile} - #{2 * $hamburger-menu-icon-spacing} - #{$hamburger-menu-icon-width} - #{$edit-icon-width} - #{2 * $navbar-icon-width} - #{$vikunja-nav-logo-full-width} - var(--nav-username-width));
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $tablet) {
|
@media screen and (max-width: $tablet) {
|
||||||
// We need a fixed width for overflowing ellipsis to work
|
// We need a fixed width for overflowing ellipsis to work
|
||||||
width: calc(100vw - #{$user-dropdown-width-mobile} - #{2 * $hamburger-menu-icon-spacing} - #{$hamburger-menu-icon-width} - #{$edit-icon-width} - #{2 * $navbar-icon-width});
|
width: calc(100vw - #{$user-dropdown-width-mobile} - #{2 * $hamburger-menu-icon-spacing} - #{$hamburger-menu-icon-width} - #{$edit-icon-width} - #{2 * $navbar-icon-width});
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.dropdown-trigger) {
|
:deep(.dropdown-trigger) {
|
||||||
color: $grey-400;
|
color: var(--grey-400);
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -57,7 +57,7 @@ export default {
|
||||||
padding: 0 0 0 .5rem;
|
padding: 0 0 0 .5rem;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
color: $grey-900;
|
color: var(--grey-900);
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
@media screen and (max-width: $desktop) {
|
@media screen and (max-width: $desktop) {
|
||||||
|
|
|
@ -82,11 +82,11 @@ export default {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
height: $button-height;
|
height: $button-height;
|
||||||
box-shadow: $shadow-sm;
|
box-shadow: var(--shadow-sm);
|
||||||
|
|
||||||
&.is-hovered,
|
&.is-hovered,
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: $shadow-md;
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fullheight {
|
&.fullheight {
|
||||||
|
@ -99,11 +99,11 @@ export default {
|
||||||
&:active,
|
&:active,
|
||||||
&:focus,
|
&:focus,
|
||||||
&:focus:not(:active) {
|
&:focus:not(:active) {
|
||||||
box-shadow: $shadow-xs !important;
|
box-shadow: var(--shadow-xs) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-primary.is-outlined:hover {
|
&.is-primary.is-outlined:hover {
|
||||||
color: $white;
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-small {
|
&.is-small {
|
||||||
|
|
|
@ -134,7 +134,7 @@ export default {
|
||||||
height: $PICKER_SIZE;
|
height: $PICKER_SIZE;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
border: $BORDER_WIDTH solid $grey-300;
|
border: $BORDER_WIDTH solid var(--grey-300);
|
||||||
box-shadow: $shadow;
|
box-shadow: $shadow;
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
|
|
|
@ -258,7 +258,7 @@ export default {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
width: 320px;
|
width: 320px;
|
||||||
background: $white;
|
background: var(--white);
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
box-shadow: $shadow;
|
box-shadow: $shadow;
|
||||||
|
|
||||||
|
@ -272,7 +272,7 @@ export default {
|
||||||
padding: 0 .5rem;
|
padding: 0 .5rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 2.25rem;
|
height: 2.25rem;
|
||||||
color: $text;
|
color: var(--text);
|
||||||
transition: all $transition;
|
transition: all $transition;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
|
@ -280,7 +280,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $light;
|
background: var(--light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
|
@ -291,7 +291,7 @@ export default {
|
||||||
padding-right: .25rem;
|
padding-right: .25rem;
|
||||||
|
|
||||||
.weekday {
|
.weekday {
|
||||||
color: $text-light;
|
color: var(--text-light);
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -338,13 +338,14 @@ $editor-border-color: #ddd;
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
border: 1px solid $editor-border-color;
|
border: 1px solid $editor-border-color;
|
||||||
|
background: var(--white);
|
||||||
|
|
||||||
&-lines pre {
|
&-lines pre {
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-placeholder {
|
&-placeholder {
|
||||||
color: $grey-400 !important;
|
color: var(--grey-400) !important;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -383,7 +384,7 @@ $editor-border-color: #ddd;
|
||||||
|
|
||||||
pre.CodeMirror-line {
|
pre.CodeMirror-line {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
color: $grey-700 !important;
|
color: var(--grey-700) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-header {
|
.cm-header {
|
||||||
|
@ -409,10 +410,10 @@ ul.actions {
|
||||||
}
|
}
|
||||||
|
|
||||||
&, a {
|
&, a {
|
||||||
color: $grey-500;
|
color: var(--grey-500);
|
||||||
|
|
||||||
&.done-edit {
|
&.done-edit {
|
||||||
color: $primary;
|
color: var(--primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ svg {
|
||||||
}
|
}
|
||||||
|
|
||||||
.check:hover svg {
|
.check:hover svg {
|
||||||
stroke: $primary;
|
stroke: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-disabled .check:hover svg {
|
.is-disabled .check:hover svg {
|
||||||
|
@ -125,7 +125,7 @@ polyline {
|
||||||
|
|
||||||
input[type=checkbox]:checked + .check {
|
input[type=checkbox]:checked + .check {
|
||||||
svg {
|
svg {
|
||||||
stroke: $primary;
|
stroke: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
path {
|
path {
|
||||||
|
|
|
@ -380,23 +380,23 @@ export default {
|
||||||
|
|
||||||
&.has-search-results .input-wrapper {
|
&.has-search-results .input-wrapper {
|
||||||
border-radius: $radius $radius 0 0;
|
border-radius: $radius $radius 0 0;
|
||||||
border-color: $primary !important;
|
border-color: var(--primary) !important;
|
||||||
background: $white !important;
|
background: var(--white) !important;
|
||||||
|
|
||||||
&, &:focus-within {
|
&, &:focus-within {
|
||||||
border-bottom-color: $grey-200 !important;
|
border-bottom-color: var(--grey-200) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-wrapper {
|
.input-wrapper {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: $white !important;
|
background: var(--white) !important;
|
||||||
border-color: $grey-200 !important;
|
border-color: var(--grey-200) !important;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: $grey-300 !important;
|
border-color: var(--grey-300) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
|
@ -422,8 +422,8 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus-within {
|
&:focus-within {
|
||||||
border-color: $primary !important;
|
border-color: var(--primary) !important;
|
||||||
background: $white !important;
|
background: var(--white) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader {
|
.loader {
|
||||||
|
@ -432,9 +432,9 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-results {
|
.search-results {
|
||||||
background: $white;
|
background: var(--white);
|
||||||
border-radius: 0 0 $radius $radius;
|
border-radius: 0 0 $radius $radius;
|
||||||
border: 1px solid $primary;
|
border: 1px solid var(--primary);
|
||||||
border-top: none;
|
border-top: none;
|
||||||
|
|
||||||
max-height: 50vh;
|
max-height: 50vh;
|
||||||
|
@ -481,16 +481,16 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus, &:hover {
|
&:focus, &:hover {
|
||||||
background: $grey-100;
|
background: var(--grey-100);
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
|
|
||||||
.hint-text {
|
.hint-text {
|
||||||
color: $text;
|
color: var(--text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background: $grey-200;
|
background: var(--grey-200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,49 @@
|
||||||
<template>
|
<template>
|
||||||
<transition name="fade">
|
<x-button
|
||||||
<filters
|
v-if="hasFilters"
|
||||||
v-if="visibleInternal"
|
type="secondary"
|
||||||
v-model="value"
|
@click="clearFilters"
|
||||||
ref="filters"
|
>
|
||||||
/>
|
{{ $t('filters.clear') }}
|
||||||
</transition>
|
</x-button>
|
||||||
|
<popup>
|
||||||
|
<template #trigger="{toggle}">
|
||||||
|
<x-button
|
||||||
|
@click.prevent.stop="toggle()"
|
||||||
|
type="secondary"
|
||||||
|
icon="filter"
|
||||||
|
>
|
||||||
|
{{ $t('filters.title') }}
|
||||||
|
</x-button>
|
||||||
|
</template>
|
||||||
|
<template #content="{isOpen}">
|
||||||
|
<filters
|
||||||
|
v-model="value"
|
||||||
|
ref="filters"
|
||||||
|
class="filter-popup"
|
||||||
|
:class="{'is-open': isOpen}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</popup>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
import Filters from '@/components/list/partials/filters'
|
||||||
import Filters from '../../../components/list/partials/filters'
|
import {getDefaultParams} from '@/components/tasks/mixins/taskList'
|
||||||
|
import Popup from '@/components/misc/popup'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'filter-popup',
|
name: 'filter-popup',
|
||||||
components: {
|
components: {
|
||||||
|
Popup,
|
||||||
Filters,
|
Filters,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
visible: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue'],
|
emits: ['update:modelValue'],
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
visibleInternal: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
value: {
|
value: {
|
||||||
get() {
|
get() {
|
||||||
|
@ -41,34 +53,46 @@ export default {
|
||||||
this.$emit('update:modelValue', value)
|
this.$emit('update:modelValue', value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
hasFilters() {
|
||||||
mounted() {
|
// this.value also contains the page parameter which we don't want to include in filters
|
||||||
document.addEventListener('click', this.hidePopup)
|
// eslint-disable-next-line no-unused-vars
|
||||||
},
|
const {filter_by, filter_value, filter_comparator, filter_concat, s} = this.value
|
||||||
beforeUnmount() {
|
const def = {...getDefaultParams()}
|
||||||
document.removeEventListener('click', this.hidePopup)
|
|
||||||
|
const params = {filter_by, filter_value, filter_comparator, filter_concat, s}
|
||||||
|
const defaultParams = {
|
||||||
|
filter_by: def.filter_by,
|
||||||
|
filter_value: def.filter_value,
|
||||||
|
filter_comparator: def.filter_comparator,
|
||||||
|
filter_concat: def.filter_concat,
|
||||||
|
s: s ? def.s : undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(params) !== JSON.stringify(defaultParams)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
handler(value) {
|
handler(value) {
|
||||||
this.params = value
|
this.value = value
|
||||||
},
|
},
|
||||||
immediate: true,
|
immediate: true,
|
||||||
},
|
},
|
||||||
visible() {
|
|
||||||
this.visibleInternal = !this.visibleInternal
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
hidePopup(e) {
|
clearFilters() {
|
||||||
if (!this.visibleInternal) {
|
this.value = {...getDefaultParams()}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
closeWhenClickedOutside(e, this.$refs.filters.$el, () => {
|
|
||||||
this.visibleInternal = false
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.filter-popup {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
&.is-open {
|
||||||
|
margin: 2rem 0 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -189,6 +189,8 @@ import ListService from '@/services/list'
|
||||||
import NamespaceService from '@/services/namespace'
|
import NamespaceService from '@/services/namespace'
|
||||||
import EditLabels from '@/components/tasks/partials/editLabels.vue'
|
import EditLabels from '@/components/tasks/partials/editLabels.vue'
|
||||||
|
|
||||||
|
import {objectToSnakeCase} from '@/helpers/case'
|
||||||
|
|
||||||
// FIXME: merge with DEFAULT_PARAMS in taskList.js
|
// FIXME: merge with DEFAULT_PARAMS in taskList.js
|
||||||
const DEFAULT_PARAMS = {
|
const DEFAULT_PARAMS = {
|
||||||
sort_by: [],
|
sort_by: [],
|
||||||
|
@ -261,7 +263,9 @@ export default {
|
||||||
watch: {
|
watch: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
handler(value) {
|
handler(value) {
|
||||||
this.params = value
|
// FIXME: filters should only be converted to snake case in
|
||||||
|
// the last moment
|
||||||
|
this.params = objectToSnakeCase(value)
|
||||||
this.prepareFilters()
|
this.prepareFilters()
|
||||||
},
|
},
|
||||||
immediate: true,
|
immediate: true,
|
||||||
|
@ -458,15 +462,7 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let foundDone = false
|
this.filters.done = this.params.filter_by.some((f) => f === 'done') === false
|
||||||
this.params.filter_by.forEach((f, i) => {
|
|
||||||
if (f === 'done') {
|
|
||||||
foundDone = i
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (foundDone === false) {
|
|
||||||
this.filters.done = true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async prepareRelatedObjectFilter(kind, filterName = null, servicePrefix = null) {
|
async prepareRelatedObjectFilter(kind, filterName = null, servicePrefix = null) {
|
||||||
if (filterName === null) {
|
if (filterName === null) {
|
||||||
|
|
|
@ -86,11 +86,11 @@ export default {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: calc((100% - #{($lists-per-row - 1) * 1rem}) / #{$lists-per-row});
|
width: calc((100% - #{($lists-per-row - 1) * 1rem}) / #{$lists-per-row});
|
||||||
height: $list-height;
|
height: $list-height;
|
||||||
background: $white;
|
background: var(--white);
|
||||||
margin: 0 $list-spacing $list-spacing 0;
|
margin: 0 $list-spacing $list-spacing 0;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
box-shadow: $shadow-sm;
|
box-shadow: var(--shadow-sm);
|
||||||
transition: box-shadow $transition;
|
transition: box-shadow $transition;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -98,13 +98,13 @@ export default {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: $shadow-md;
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active,
|
&:active,
|
||||||
&:focus,
|
&:focus,
|
||||||
&:focus:not(:active) {
|
&:focus:not(:active) {
|
||||||
box-shadow: $shadow-xs !important;
|
box-shadow: var(--shadow-xs) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: $widescreen) {
|
@media screen and (min-width: $widescreen) {
|
||||||
|
@ -158,7 +158,7 @@ export default {
|
||||||
font-family: $vikunja-font;
|
font-family: $vikunja-font;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
color: $text;
|
color: var(--text);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
max-height: calc(100% - 2rem); // 1rem padding, 1rem height of the "is archived" badge
|
max-height: calc(100% - 2rem); // 1rem padding, 1rem height of the "is archived" badge
|
||||||
|
@ -171,7 +171,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-light-text .title {
|
&.has-light-text .title {
|
||||||
color: $light;
|
color: var(--light);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-background {
|
&.has-background {
|
||||||
|
@ -180,8 +180,8 @@ export default {
|
||||||
background-position: center;
|
background-position: center;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
text-shadow: 0 0 10px $black, 1px 1px 5px $grey-700, -1px -1px 5px $grey-700;
|
text-shadow: 0 0 10px var(--black), 1px 1px 5px var(--grey-700), -1px -1px 5px var(--grey-700);
|
||||||
color: $white;
|
color: var(--white);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ export default {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $orange;
|
color: var(--warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-archived {
|
&.is-archived {
|
||||||
|
@ -200,7 +200,7 @@ export default {
|
||||||
&.is-favorite {
|
&.is-favorite {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
color: $orange;
|
color: var(--warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,8 +40,8 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.is-done {
|
.is-done {
|
||||||
background: $green;
|
background: var(--success);
|
||||||
color: $white;
|
color: var(--white);
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<i18n-t keypath="apiConfig.signInOn">
|
<i18n-t keypath="apiConfig.signInOn">
|
||||||
<span class="url" v-tooltip="apiUrl"> {{ apiDomain }} </span>
|
<span class="url" v-tooltip="apiUrl"> {{ apiDomain }} </span>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
<br />
|
<br/>
|
||||||
<a @click="() => (configureApi = true)">{{ $t('apiConfig.change') }}</a>
|
<a @click="() => (configureApi = true)">{{ $t('apiConfig.change') }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -46,9 +46,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { parseURL } from 'ufo'
|
import {parseURL} from 'ufo'
|
||||||
|
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
|
||||||
const API_DEFAULT_PORT = 3456
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'apiConfig',
|
name: 'apiConfig',
|
||||||
|
@ -68,131 +67,51 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
apiDomain() {
|
apiDomain() {
|
||||||
return parseURL(this.apiUrl).host
|
return parseURL(this.apiUrl).host || parseURL(window.location.href).host
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
configureOpen: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
configureOpen: {
|
||||||
|
handler(value) {
|
||||||
|
this.configureApi = value
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setApiUrl() {
|
async setApiUrl() {
|
||||||
if (this.apiUrl === '') {
|
if (this.apiUrl === '') {
|
||||||
|
// Don't try to check and set an empty url
|
||||||
|
this.errorMsg = this.$t('apiConfig.urlRequired')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let urlToCheck = this.apiUrl
|
try {
|
||||||
|
const url = await checkAndSetApiUrl(this.apiUrl)
|
||||||
|
|
||||||
// Check if the url has an http prefix
|
if (url === '') {
|
||||||
if (
|
// If the config setter function could not figure out a url
|
||||||
!urlToCheck.startsWith('http://') &&
|
throw new Error('URL cannot be empty.')
|
||||||
!urlToCheck.startsWith('https://')
|
}
|
||||||
) {
|
|
||||||
urlToCheck = `http://${urlToCheck}`
|
// Set it + save it to local storage to save us the hoops
|
||||||
|
this.errorMsg = ''
|
||||||
|
this.successMsg = this.$t('apiConfig.success', {domain: this.apiDomain})
|
||||||
|
this.configureApi = false
|
||||||
|
this.apiUrl = url
|
||||||
|
this.$emit('foundApi', this.apiUrl)
|
||||||
|
} catch (e) {
|
||||||
|
// Still not found, url is still invalid
|
||||||
|
this.successMsg = ''
|
||||||
|
this.errorMsg = this.$t('apiConfig.error', {domain: this.apiDomain})
|
||||||
}
|
}
|
||||||
|
|
||||||
urlToCheck = new URL(urlToCheck)
|
|
||||||
const origUrlToCheck = urlToCheck
|
|
||||||
|
|
||||||
const oldUrl = window.API_URL
|
|
||||||
window.API_URL = urlToCheck.toString()
|
|
||||||
|
|
||||||
// Check if the api is reachable at the provided url
|
|
||||||
this.$store
|
|
||||||
.dispatch('config/update')
|
|
||||||
.catch((e) => {
|
|
||||||
// Check if it is reachable at /api/v1 and http
|
|
||||||
if (
|
|
||||||
!urlToCheck.pathname.endsWith('/api/v1') &&
|
|
||||||
!urlToCheck.pathname.endsWith('/api/v1/')
|
|
||||||
) {
|
|
||||||
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
|
|
||||||
window.API_URL = urlToCheck.toString()
|
|
||||||
return this.$store.dispatch('config/update')
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
// Check if it has a port and if not check if it is reachable at https
|
|
||||||
if (urlToCheck.protocol === 'http:') {
|
|
||||||
urlToCheck.protocol = 'https:'
|
|
||||||
window.API_URL = urlToCheck.toString()
|
|
||||||
return this.$store.dispatch('config/update')
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
// Check if it is reachable at /api/v1 and https
|
|
||||||
urlToCheck.pathname = origUrlToCheck.pathname
|
|
||||||
if (
|
|
||||||
!urlToCheck.pathname.endsWith('/api/v1') &&
|
|
||||||
!urlToCheck.pathname.endsWith('/api/v1/')
|
|
||||||
) {
|
|
||||||
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
|
|
||||||
window.API_URL = urlToCheck.toString()
|
|
||||||
return this.$store.dispatch('config/update')
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
// Check if it is reachable at port API_DEFAULT_PORT and https
|
|
||||||
if (urlToCheck.port !== API_DEFAULT_PORT) {
|
|
||||||
urlToCheck.protocol = 'https:'
|
|
||||||
urlToCheck.port = API_DEFAULT_PORT
|
|
||||||
window.API_URL = urlToCheck.toString()
|
|
||||||
return this.$store.dispatch('config/update')
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1 and https
|
|
||||||
urlToCheck.pathname = origUrlToCheck.pathname
|
|
||||||
if (
|
|
||||||
!urlToCheck.pathname.endsWith('/api/v1') &&
|
|
||||||
!urlToCheck.pathname.endsWith('/api/v1/')
|
|
||||||
) {
|
|
||||||
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
|
|
||||||
window.API_URL = urlToCheck.toString()
|
|
||||||
return this.$store.dispatch('config/update')
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
// Check if it is reachable at port API_DEFAULT_PORT and http
|
|
||||||
if (urlToCheck.port !== API_DEFAULT_PORT) {
|
|
||||||
urlToCheck.protocol = 'http:'
|
|
||||||
urlToCheck.port = API_DEFAULT_PORT
|
|
||||||
window.API_URL = urlToCheck.toString()
|
|
||||||
return this.$store.dispatch('config/update')
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1 and http
|
|
||||||
urlToCheck.pathname = origUrlToCheck.pathname
|
|
||||||
if (
|
|
||||||
!urlToCheck.pathname.endsWith('/api/v1') &&
|
|
||||||
!urlToCheck.pathname.endsWith('/api/v1/')
|
|
||||||
) {
|
|
||||||
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
|
|
||||||
window.API_URL = urlToCheck.toString()
|
|
||||||
return this.$store.dispatch('config/update')
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// Still not found, url is still invalid
|
|
||||||
this.successMsg = ''
|
|
||||||
this.errorMsg = this.$t('apiConfig.error', {domain: this.apiDomain})
|
|
||||||
window.API_URL = oldUrl
|
|
||||||
})
|
|
||||||
.then((r) => {
|
|
||||||
if (typeof r !== 'undefined') {
|
|
||||||
// Set it + save it to local storage to save us the hoops
|
|
||||||
this.errorMsg = ''
|
|
||||||
this.successMsg = this.$t('apiConfig.success', {domain: this.apiDomain})
|
|
||||||
localStorage.setItem('API_URL', window.API_URL)
|
|
||||||
this.configureApi = false
|
|
||||||
this.apiUrl = window.API_URL
|
|
||||||
this.$emit('foundApi', this.apiUrl)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -200,15 +119,15 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.api-config {
|
.api-config {
|
||||||
margin-bottom: .75rem;
|
margin-bottom: .75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.api-url-info {
|
.api-url-info {
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
span.url {
|
.url {
|
||||||
border-bottom: 1px dashed $primary;
|
border-bottom: 1px dashed var(--primary);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -4,7 +4,13 @@
|
||||||
<p class="card-header-title">
|
<p class="card-header-title">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</p>
|
</p>
|
||||||
<a @click="$emit('close')" class="card-header-icon" v-if="hasClose">
|
<a
|
||||||
|
v-if="hasClose"
|
||||||
|
class="card-header-icon"
|
||||||
|
:aria-label="$t('misc.close')"
|
||||||
|
@click="$emit('close')"
|
||||||
|
v-tooltip="$t('misc.close')"
|
||||||
|
>
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<icon :icon="closeIcon"/>
|
<icon :icon="closeIcon"/>
|
||||||
</span>
|
</span>
|
||||||
|
@ -36,7 +42,7 @@ export default {
|
||||||
},
|
},
|
||||||
closeIcon: {
|
closeIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'angle-right',
|
default: 'times',
|
||||||
},
|
},
|
||||||
shadow: {
|
shadow: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -57,22 +63,22 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.card {
|
.card {
|
||||||
background-color: $white;
|
background-color: var(--white);
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
border: 1px solid $grey-200;
|
border: 1px solid var(--card-border-color);
|
||||||
box-shadow: $shadow-sm;
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border-bottom: 1px solid $grey-200;
|
border-bottom: 1px solid var(--card-border-color);
|
||||||
border-radius: $radius $radius 0 0;
|
border-radius: $radius $radius 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: should maybe be merged somehow with modal
|
// FIXME: should maybe be merged somehow with modal
|
||||||
:deep(.modal-card-foot) {
|
:deep(.modal-card-foot) {
|
||||||
background-color: $grey-50;
|
background-color: var(--grey-50);
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -6,7 +6,6 @@
|
||||||
:padding="false"
|
:padding="false"
|
||||||
class="has-text-left has-overflow"
|
class="has-text-left has-overflow"
|
||||||
:has-close="true"
|
:has-close="true"
|
||||||
close-icon="times"
|
|
||||||
@close="$router.back()"
|
@close="$router.back()"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
<template>
|
|
||||||
<modal @close="close()">
|
|
||||||
<card class="has-background-white has-no-shadow" :title="$t('keyboardShortcuts.title')">
|
|
||||||
<div class="message is-primary">
|
|
||||||
<div class="message-body">
|
|
||||||
{{ $t('keyboardShortcuts.allPages') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
<strong>{{ $t('keyboardShortcuts.toggleMenu') }}</strong>
|
|
||||||
<shortcut :keys="['ctrl', 'e']"/>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>{{ $t('keyboardShortcuts.quickSearch') }}</strong>
|
|
||||||
<shortcut :keys="['ctrl', 'k']"/>
|
|
||||||
</p>
|
|
||||||
<h3>{{ $t('list.kanban.title') }}</h3>
|
|
||||||
<div class="message is-primary" v-if="$route.name === 'list.kanban'">
|
|
||||||
<div class="message-body">
|
|
||||||
{{ $t('keyboardShortcuts.currentPageOnly') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
<strong>{{ $t('keyboardShortcuts.task.done') }}</strong>
|
|
||||||
<shortcut :keys="['ctrl', 'click']"/>
|
|
||||||
</p>
|
|
||||||
<h3>{{ $t('keyboardShortcuts.task.title') }}</h3>
|
|
||||||
<div
|
|
||||||
class="message is-primary"
|
|
||||||
v-if="$route.name === 'task.detail' || $route.name === 'task.list.detail' || $route.name === 'task.gantt.detail' || $route.name === 'task.kanban.detail' || $route.name === 'task.detail'">
|
|
||||||
<div class="message-body">
|
|
||||||
{{ $t('keyboardShortcuts.currentPageOnly') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
<strong>{{ $t('keyboardShortcuts.task.assign') }}</strong>
|
|
||||||
<shortcut :keys="['a']"/>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>{{ $t('keyboardShortcuts.task.labels') }}</strong>
|
|
||||||
<shortcut :keys="['l']"/>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>{{ $t('keyboardShortcuts.task.dueDate') }}</strong>
|
|
||||||
<shortcut :keys="['d']"/>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>{{ $t('keyboardShortcuts.task.attachment') }}</strong>
|
|
||||||
<shortcut :keys="['f']"/>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>{{ $t('keyboardShortcuts.task.related') }}</strong>
|
|
||||||
<shortcut :keys="['r']"/>
|
|
||||||
</p>
|
|
||||||
</card>
|
|
||||||
</modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {KEYBOARD_SHORTCUTS_ACTIVE} from '@/store/mutation-types'
|
|
||||||
import Shortcut from '@/components/misc/shortcut.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'keyboard-shortcuts',
|
|
||||||
components: {Shortcut},
|
|
||||||
methods: {
|
|
||||||
close() {
|
|
||||||
this.$store.commit(KEYBOARD_SHORTCUTS_ACTIVE, false)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
<template>
|
||||||
|
<modal @close="close()">
|
||||||
|
<card class="has-background-white has-no-shadow keyboard-shortcuts" :title="$t('keyboardShortcuts.title')">
|
||||||
|
<template v-for="(s, i) in shortcuts" :key="i">
|
||||||
|
<h3>{{ $t(s.title) }}</h3>
|
||||||
|
|
||||||
|
<div class="message is-primary">
|
||||||
|
<div class="message-body">
|
||||||
|
{{
|
||||||
|
s.available($route) ? $t('keyboardShortcuts.currentPageOnly') : $t('keyboardShortcuts.allPages')
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<dl class="shortcut-list">
|
||||||
|
<template v-for="(sc, si) in s.shortcuts" :key="si">
|
||||||
|
<dt class="shortcut-title">{{ $t(sc.title) }}</dt>
|
||||||
|
<shortcut
|
||||||
|
class="shortcut-keys"
|
||||||
|
is="dd"
|
||||||
|
:keys="sc.keys"
|
||||||
|
:combination="typeof sc.combination !== 'undefined' ? $t(`keyboardShortcuts.${sc.combination}`) : null"/>
|
||||||
|
</template>
|
||||||
|
</dl>
|
||||||
|
</template>
|
||||||
|
</card>
|
||||||
|
</modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {KEYBOARD_SHORTCUTS_ACTIVE} from '@/store/mutation-types'
|
||||||
|
import Shortcut from '@/components/misc/shortcut.vue'
|
||||||
|
import {KEYBOARD_SHORTCUTS} from './shortcuts'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'keyboard-shortcuts',
|
||||||
|
components: {Shortcut},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
shortcuts: KEYBOARD_SHORTCUTS,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.$store.commit(KEYBOARD_SHORTCUTS_ACTIVE, false)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.keyboard-shortcuts {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message:not(:last-child) {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-body {
|
||||||
|
padding: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortcut-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortcut-title {
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortcut-keys {
|
||||||
|
justify-content: end;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,88 @@
|
||||||
|
import {isAppleDevice} from '@/helpers/isAppleDevice'
|
||||||
|
|
||||||
|
const ctrl = isAppleDevice() ? '⌘' : 'ctrl'
|
||||||
|
|
||||||
|
export const KEYBOARD_SHORTCUTS = [
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.general',
|
||||||
|
available: () => null,
|
||||||
|
shortcuts: [
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.toggleMenu',
|
||||||
|
keys: [ctrl, 'e'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.quickSearch',
|
||||||
|
keys: [ctrl, 'k'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'list.kanban.title',
|
||||||
|
available: (route) => route.name === 'list.kanban',
|
||||||
|
shortcuts: [
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.task.done',
|
||||||
|
keys: [ctrl, 'click'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.list.title',
|
||||||
|
available: (route) => route.name.startsWith('list.'),
|
||||||
|
shortcuts: [
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.list.switchToListView',
|
||||||
|
keys: ['g', 'l'],
|
||||||
|
combination: 'then',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.list.switchToGanttView',
|
||||||
|
keys: ['g', 'g'],
|
||||||
|
combination: 'then',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.list.switchToTableView',
|
||||||
|
keys: ['g', 't'],
|
||||||
|
combination: 'then',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.list.switchToKanbanView',
|
||||||
|
keys: ['g', 'k'],
|
||||||
|
combination: 'then',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.task.title',
|
||||||
|
available: (route) => [
|
||||||
|
'task.detail',
|
||||||
|
'task.list.detail',
|
||||||
|
'task.gantt.detail',
|
||||||
|
'task.kanban.detail',
|
||||||
|
'task.detail',
|
||||||
|
].includes(route.name),
|
||||||
|
shortcuts: [
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.task.assign',
|
||||||
|
keys: ['a'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.task.labels',
|
||||||
|
keys: ['l'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.task.dueDate',
|
||||||
|
keys: ['d'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.task.attachment',
|
||||||
|
keys: ['f'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'keyboardShortcuts.task.related',
|
||||||
|
keys: ['r'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
|
@ -22,7 +22,7 @@ export default {
|
||||||
.legal-links {
|
.legal-links {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
color: $grey-300;
|
color: var(--grey-300);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,43 @@
|
||||||
|
<template>
|
||||||
|
<div class="no-auth-wrapper">
|
||||||
|
<div class="noauth-container">
|
||||||
|
<Logo class="logo" width="400" height="117" />
|
||||||
|
<div class="message is-info" v-if="motd !== ''">
|
||||||
|
<div class="message-header">
|
||||||
|
<p>{{ $t('misc.info') }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="message-body">
|
||||||
|
{{ motd }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Logo from '@/components/home/Logo.vue'
|
||||||
|
import {useStore} from 'vuex'
|
||||||
|
import {computed} from 'vue'
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const motd = computed(() => store.state.config.motd)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.no-auth-wrapper {
|
||||||
|
background: url('@/assets/llama.svg') no-repeat bottom left fixed var(--site-background);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noauth-container {
|
||||||
|
max-width: 450px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
color: var(--logo-text-color);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -89,7 +89,7 @@ export default {
|
||||||
},
|
},
|
||||||
currentPage: {
|
currentPage: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
default: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
<template>
|
||||||
|
<slot name="trigger" :isOpen="open" :toggle="toggle"></slot>
|
||||||
|
<div class="popup" :class="{'is-open': open}" ref="popup">
|
||||||
|
<slot name="content" :isOpen="open"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
||||||
|
import {onBeforeUnmount, onMounted, ref} from 'vue'
|
||||||
|
|
||||||
|
const open = ref(false)
|
||||||
|
const popup = ref(null)
|
||||||
|
|
||||||
|
const toggle = () => {
|
||||||
|
open.value = !open.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function hidePopup(e) {
|
||||||
|
if (!open.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// we actually want to use popup.$el, not its value.
|
||||||
|
// eslint-disable-next-line vue/no-ref-as-operand
|
||||||
|
closeWhenClickedOutside(e, popup.value, () => {
|
||||||
|
open.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('click', hidePopup)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
document.removeEventListener('click', hidePopup)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.popup {
|
||||||
|
transition: opacity $transition;
|
||||||
|
opacity: 0;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
|
||||||
|
&.is-open {
|
||||||
|
opacity: 1;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,143 @@
|
||||||
|
<template>
|
||||||
|
<!-- This is a workaround to get the sw to "see" the to-be-cached version of the offline background image -->
|
||||||
|
<div class="offline" style="height: 0;width: 0;"></div>
|
||||||
|
<div class="app offline" v-if="!online">
|
||||||
|
<div class="offline-message">
|
||||||
|
<h1>{{ $t('offline.title') }}</h1>
|
||||||
|
<p>{{ $t('offline.text') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template v-else-if="ready">
|
||||||
|
<slot/>
|
||||||
|
</template>
|
||||||
|
<section v-else-if="error !== ''">
|
||||||
|
<no-auth-wrapper>
|
||||||
|
<card>
|
||||||
|
<p v-if="error === errorNoApiUrl">
|
||||||
|
{{ $t('ready.noApiUrlConfigured') }}
|
||||||
|
</p>
|
||||||
|
<div class="notification is-danger" v-else>
|
||||||
|
<p>
|
||||||
|
{{ $t('ready.errorOccured') }}<br/>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ $t('ready.checkApiUrl') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<api-config :configure-open="true" @found-api="load"/>
|
||||||
|
</card>
|
||||||
|
</no-auth-wrapper>
|
||||||
|
</section>
|
||||||
|
<transition name="fade">
|
||||||
|
<section class="vikunja-loading" v-if="showLoading">
|
||||||
|
<Logo class="logo"/>
|
||||||
|
<p>
|
||||||
|
<span class="loader-container is-loading-small is-loading"></span>
|
||||||
|
{{ $t('ready.loading') }}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Logo from '@/assets/logo.svg?component'
|
||||||
|
import ApiConfig from '@/components/misc/api-config'
|
||||||
|
import NoAuthWrapper from '@/components/misc/no-auth-wrapper'
|
||||||
|
import {mapState} from 'vuex'
|
||||||
|
import {ERROR_NO_API_URL} from '@/helpers/checkAndSetApiUrl'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ready',
|
||||||
|
components: {
|
||||||
|
Logo,
|
||||||
|
NoAuthWrapper,
|
||||||
|
ApiConfig,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
error: '',
|
||||||
|
errorNoApiUrl: ERROR_NO_API_URL,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.load()
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
ready() {
|
||||||
|
return this.$store.state.vikunjaReady
|
||||||
|
},
|
||||||
|
showLoading() {
|
||||||
|
return !this.ready && this.error === ''
|
||||||
|
},
|
||||||
|
...mapState([
|
||||||
|
'online',
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
load() {
|
||||||
|
this.$store.dispatch('loadApp')
|
||||||
|
.catch(e => {
|
||||||
|
this.error = e
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.vikunja-loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
flex-direction: column;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background: var(--grey-100);
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-container {
|
||||||
|
margin-right: 1rem;
|
||||||
|
|
||||||
|
&.is-loading::after {
|
||||||
|
border-left-color: var(--grey-400);
|
||||||
|
border-bottom-color: var(--grey-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline {
|
||||||
|
background: url('@/assets/llama-nightscape.jpg') no-repeat center;
|
||||||
|
background-size: cover;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline-message {
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
width: 100vw;
|
||||||
|
bottom: 5vh;
|
||||||
|
color: $white;
|
||||||
|
padding: 0 1rem;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
color: $white;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<span class="shortcuts">
|
<component :is="is" class="shortcuts">
|
||||||
<template v-for="(k, i) in keys" :key="i">
|
<template v-for="(k, i) in keys" :key="i">
|
||||||
<kbd>{{ k }}</kbd>
|
<kbd>{{ k }}</kbd>
|
||||||
<span v-if="i < keys.length - 1">+</span>
|
<span v-if="i < keys.length - 1">{{ combination }}</span>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -15,6 +15,14 @@ export default {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
combination: {
|
||||||
|
type: String,
|
||||||
|
default: '+',
|
||||||
|
},
|
||||||
|
is: {
|
||||||
|
type: String,
|
||||||
|
default: 'div',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -27,8 +35,8 @@ export default {
|
||||||
|
|
||||||
kbd {
|
kbd {
|
||||||
padding: .1rem .35rem;
|
padding: .1rem .35rem;
|
||||||
border: 1px solid $grey-300;
|
border: 1px solid var(--grey-300);
|
||||||
background: $grey-100;
|
background: var(--grey-100);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,7 @@
|
||||||
class="modal-container"
|
class="modal-container"
|
||||||
:class="{'has-overflow': overflow}"
|
:class="{'has-overflow': overflow}"
|
||||||
@click.self.prevent.stop="$emit('close')"
|
@click.self.prevent.stop="$emit('close')"
|
||||||
@shortkey="$emit('close')"
|
v-shortcut="'Escape'"
|
||||||
v-shortkey="['esc']"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="modal-content"
|
class="modal-content"
|
||||||
|
|
|
@ -25,15 +25,7 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
namespaces() {
|
namespaces() {
|
||||||
if (this.query === '') {
|
return this.$store.getters['namespaces/searchNamespace'](this.query)
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.$store.state.namespaces.namespaces.filter(n => {
|
|
||||||
return !n.isArchived &&
|
|
||||||
n.id > 0 &&
|
|
||||||
n.title.toLowerCase().includes(this.query.toLowerCase())
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -145,9 +145,9 @@ export default {
|
||||||
width: .75rem;
|
width: .75rem;
|
||||||
height: .75rem;
|
height: .75rem;
|
||||||
|
|
||||||
background: $primary;
|
background: var(--primary);
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
border: 2px solid $white;
|
border: 2px solid var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notifications-list {
|
.notifications-list {
|
||||||
|
@ -157,12 +157,12 @@ export default {
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
background: $white;
|
background: var(--white);
|
||||||
width: 350px;
|
width: 350px;
|
||||||
max-width: calc(100vw - 2rem);
|
max-width: calc(100vw - 2rem);
|
||||||
padding: .75rem .25rem;
|
padding: .75rem .25rem;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
box-shadow: $shadow-sm;
|
box-shadow: var(--shadow-sm);
|
||||||
font-size: .85rem;
|
font-size: .85rem;
|
||||||
|
|
||||||
@media screen and (max-width: $tablet) {
|
@media screen and (max-width: $tablet) {
|
||||||
|
@ -183,14 +183,14 @@ export default {
|
||||||
transition: background-color $transition;
|
transition: background-color $transition;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $grey-100;
|
background: var(--grey-100);
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
.read-indicator {
|
.read-indicator {
|
||||||
width: .35rem;
|
width: .35rem;
|
||||||
height: .35rem;
|
height: .35rem;
|
||||||
background: $primary;
|
background: var(--primary);
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
margin-left: .5rem;
|
margin-left: .5rem;
|
||||||
|
|
||||||
|
@ -219,7 +219,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.created {
|
.created {
|
||||||
color: $grey-400;
|
color: var(--grey-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
@ -227,14 +227,14 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $grey-800;
|
color: var(--grey-800);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nothing {
|
.nothing {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
color: $grey-500;
|
color: var(--grey-500);
|
||||||
|
|
||||||
.explainer {
|
.explainer {
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
|
|
|
@ -62,7 +62,10 @@ import TeamModel from '@/models/team'
|
||||||
import {CURRENT_LIST, LOADING, LOADING_MODULE, QUICK_ACTIONS_ACTIVE} from '@/store/mutation-types'
|
import {CURRENT_LIST, LOADING, LOADING_MODULE, QUICK_ACTIONS_ACTIVE} from '@/store/mutation-types'
|
||||||
import ListModel from '@/models/list'
|
import ListModel from '@/models/list'
|
||||||
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
|
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
|
||||||
import {getHistory} from '../../modules/listHistory'
|
import {getHistory} from '@/modules/listHistory'
|
||||||
|
import {parseTaskText, PrefixMode} from '@/modules/parseTaskText'
|
||||||
|
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
|
||||||
|
import {PREFIXES} from '@/modules/parseTaskText'
|
||||||
|
|
||||||
const TYPE_LIST = 'list'
|
const TYPE_LIST = 'list'
|
||||||
const TYPE_TASK = 'task'
|
const TYPE_TASK = 'task'
|
||||||
|
@ -107,40 +110,33 @@ export default {
|
||||||
results() {
|
results() {
|
||||||
let lists = []
|
let lists = []
|
||||||
if (this.searchMode === SEARCH_MODE_ALL || this.searchMode === SEARCH_MODE_LISTS) {
|
if (this.searchMode === SEARCH_MODE_ALL || this.searchMode === SEARCH_MODE_LISTS) {
|
||||||
let query = this.query
|
const {list} = this.parsedQuery
|
||||||
if (this.searchMode === SEARCH_MODE_LISTS) {
|
|
||||||
query = query.substr(1)
|
if (list === null) {
|
||||||
|
lists = []
|
||||||
|
} else {
|
||||||
|
const ncache = {}
|
||||||
|
const history = getHistory()
|
||||||
|
// Puts recently visited lists at the top
|
||||||
|
const allLists = [...new Set([
|
||||||
|
...history.map(l => {
|
||||||
|
return this.$store.getters['lists/getListById'](l.id)
|
||||||
|
}),
|
||||||
|
...this.$store.getters['lists/searchList'](list),
|
||||||
|
])]
|
||||||
|
|
||||||
|
lists = allLists.filter(l => {
|
||||||
|
if (typeof l === 'undefined' || l === null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof ncache[l.namespaceId] === 'undefined') {
|
||||||
|
ncache[l.namespaceId] = this.$store.getters['namespaces/getNamespaceById'](l.namespaceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return !ncache[l.namespaceId].isArchived
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const ncache = {}
|
|
||||||
|
|
||||||
const history = getHistory()
|
|
||||||
// Puts recently visited lists at the top
|
|
||||||
const allLists = [...new Set([
|
|
||||||
...history.map(l => {
|
|
||||||
return this.$store.getters['lists/getListById'](l.id)
|
|
||||||
}),
|
|
||||||
...Object.values(this.$store.state.lists)])]
|
|
||||||
|
|
||||||
lists = (allLists.filter(l => {
|
|
||||||
if (typeof l === 'undefined' || l === null) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (l.isArchived) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof ncache[l.namespaceId] === 'undefined') {
|
|
||||||
ncache[l.namespaceId] = this.$store.getters['namespaces/getNamespaceById'](l.namespaceId)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ncache[l.namespaceId].isArchived) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return l.title.toLowerCase().includes(query.toLowerCase())
|
|
||||||
}) ?? [])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cmds = this.availableCmds
|
const cmds = this.availableCmds
|
||||||
|
@ -207,7 +203,9 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.$t('quickActions.hint')
|
const prefixes = PREFIXES[getQuickAddMagicMode()] ?? PREFIXES[PrefixMode.Default]
|
||||||
|
|
||||||
|
return this.$t('quickActions.hint', prefixes)
|
||||||
},
|
},
|
||||||
currentList() {
|
currentList() {
|
||||||
return Object.keys(this.$store.state[CURRENT_LIST]).length === 0 ? null : this.$store.state[CURRENT_LIST]
|
return Object.keys(this.$store.state[CURRENT_LIST]).length === 0 ? null : this.$store.state[CURRENT_LIST]
|
||||||
|
@ -236,18 +234,23 @@ export default {
|
||||||
|
|
||||||
return cmds
|
return cmds
|
||||||
},
|
},
|
||||||
|
parsedQuery() {
|
||||||
|
return parseTaskText(this.query, getQuickAddMagicMode())
|
||||||
|
},
|
||||||
searchMode() {
|
searchMode() {
|
||||||
if (this.query === '') {
|
if (this.query === '') {
|
||||||
return SEARCH_MODE_ALL
|
return SEARCH_MODE_ALL
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.query.startsWith('#')) {
|
const {text, list, labels, assignees} = this.parsedQuery
|
||||||
|
|
||||||
|
if (assignees.length === 0 && text !== '') {
|
||||||
return SEARCH_MODE_TASKS
|
return SEARCH_MODE_TASKS
|
||||||
}
|
}
|
||||||
if (this.query.startsWith('*')) {
|
if (assignees.length === 0 && list !== null && text === '' && labels.length === 0) {
|
||||||
return SEARCH_MODE_LISTS
|
return SEARCH_MODE_LISTS
|
||||||
}
|
}
|
||||||
if (this.query.startsWith('@')) {
|
if (assignees.length > 0 && list === null && text === '' && labels.length === 0) {
|
||||||
return SEARCH_MODE_TEAMS
|
return SEARCH_MODE_TEAMS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,12 +271,7 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let query = this.query
|
if (this.selectedCmd !== null) {
|
||||||
if (this.searchMode === SEARCH_MODE_TASKS) {
|
|
||||||
query = query.substr(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query === '' || this.selectedCmd !== null) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,8 +280,35 @@ export default {
|
||||||
this.taskSearchTimeout = null
|
this.taskSearchTimeout = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {text, list, labels} = this.parsedQuery
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
s: text,
|
||||||
|
filter_by: [],
|
||||||
|
filter_value: [],
|
||||||
|
filter_comparator: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list !== null) {
|
||||||
|
const l = this.$store.getters['lists/findListByExactname'](list)
|
||||||
|
if (l !== null) {
|
||||||
|
params.filter_by.push('list_id')
|
||||||
|
params.filter_value.push(l.id)
|
||||||
|
params.filter_comparator.push('equals')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (labels.length > 0) {
|
||||||
|
const labelIds = this.$store.getters['labels/getLabelsByExactTitles'](labels).map(l => l.id)
|
||||||
|
if (labelIds.length > 0) {
|
||||||
|
params.filter_by.push('labels')
|
||||||
|
params.filter_value.push(labelIds.join())
|
||||||
|
params.filter_comparator.push('in')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.taskSearchTimeout = setTimeout(async () => {
|
this.taskSearchTimeout = setTimeout(async () => {
|
||||||
const r = await this.taskService.getAll({}, {s: query})
|
const r = await this.taskService.getAll({}, params)
|
||||||
this.foundTasks = r.map(t => {
|
this.foundTasks = r.map(t => {
|
||||||
t.type = TYPE_TASK
|
t.type = TYPE_TASK
|
||||||
const list = this.$store.getters['lists/getListById'](t.listId)
|
const list = this.$store.getters['lists/getListById'](t.listId)
|
||||||
|
@ -301,12 +326,7 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let query = this.query
|
if (this.query === '' || this.selectedCmd !== null) {
|
||||||
if (this.searchMode === SEARCH_MODE_TEAMS) {
|
|
||||||
query = query.substr(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query === '' || this.selectedCmd !== null) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,11 +335,14 @@ export default {
|
||||||
this.teamSearchTimeout = null
|
this.teamSearchTimeout = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {assignees} = this.parsedQuery
|
||||||
|
|
||||||
this.teamSearchTimeout = setTimeout(async () => {
|
this.teamSearchTimeout = setTimeout(async () => {
|
||||||
const r = await this.teamService.getAll({}, {s: query})
|
const teamSearchPromises = assignees.map((t) => this.teamService.getAll({}, {s: t}))
|
||||||
this.foundTeams = r.map(t => {
|
const teamsResult = await Promise.all(teamSearchPromises)
|
||||||
t.title = t.name
|
this.foundTeams = teamsResult.flatMap(team => {
|
||||||
return t
|
team.title = team.name
|
||||||
|
return team
|
||||||
})
|
})
|
||||||
}, 150)
|
}, 150)
|
||||||
},
|
},
|
||||||
|
@ -348,7 +371,7 @@ export default {
|
||||||
this.doAction(this.results[0].type, this.results[0].items[0])
|
this.doAction(this.results[0].type, this.results[0].items[0])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selectedCmd === null) {
|
if (this.selectedCmd === null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -484,17 +507,19 @@ export default {
|
||||||
.active-cmd {
|
.active-cmd {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
margin-left: .5rem;
|
margin-left: .5rem;
|
||||||
|
background-color: var(--grey-100);
|
||||||
|
color: var(--grey-800);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.results {
|
.results {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: $grey-800;
|
color: var(--grey-800);
|
||||||
|
|
||||||
.result {
|
.result {
|
||||||
&-title {
|
&-title {
|
||||||
background: $grey-50;
|
background: var(--grey-50);
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
display: block;
|
display: block;
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
|
@ -505,6 +530,7 @@ export default {
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
color: var(--grey-800);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
@ -516,12 +542,12 @@ export default {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:focus, &:hover {
|
&:focus, &:hover {
|
||||||
background: $grey-50;
|
background: var(--grey-50);
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background: $grey-100;
|
background: var(--grey-100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
|
<card
|
||||||
|
class="taskedit"
|
||||||
|
:title="$t('list.list.editTask')"
|
||||||
|
@close="$emit('close')"
|
||||||
|
:has-close="true"
|
||||||
|
>
|
||||||
<form @submit.prevent="editTaskSubmit()">
|
<form @submit.prevent="editTaskSubmit()">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="tasktext">{{ $t('task.attributes.title') }}</label>
|
<label class="label" for="tasktext">{{ $t('task.attributes.title') }}</label>
|
||||||
|
@ -66,6 +72,7 @@
|
||||||
{{ $t('task.openDetail') }}
|
{{ $t('task.openDetail') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</form>
|
</form>
|
||||||
|
</card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -160,7 +167,7 @@ ul.assingees {
|
||||||
|
|
||||||
a {
|
a {
|
||||||
float: right;
|
float: right;
|
||||||
color: $red;
|
color: var(--danger);
|
||||||
transition: all $transition;
|
transition: all $transition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,12 @@
|
||||||
>
|
>
|
||||||
{{ $t('filters.title') }}
|
{{ $t('filters.title') }}
|
||||||
</x-button>
|
</x-button>
|
||||||
|
<filter-popup
|
||||||
|
:visible="showTaskFilter"
|
||||||
|
v-model="params"
|
||||||
|
@update:modelValue="loadTasks()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<filter-popup
|
|
||||||
:visible="showTaskFilter"
|
|
||||||
v-model="params"
|
|
||||||
@update:modelValue="loadTasks()"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="dates">
|
<div class="dates">
|
||||||
<template v-for="(y, yk) in days" :key="yk + 'year'">
|
<template v-for="(y, yk) in days" :key="yk + 'year'">
|
||||||
|
@ -167,15 +167,13 @@
|
||||||
</x-button>
|
</x-button>
|
||||||
</form>
|
</form>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<card
|
<edit-task
|
||||||
v-if="isTaskEdit"
|
v-if="isTaskEdit"
|
||||||
class="taskedit"
|
class="taskedit"
|
||||||
:title="$t('list.list.editTask')"
|
:title="$t('list.list.editTask')"
|
||||||
@close="() => {isTaskEdit = false;taskToEdit = null}"
|
@close="() => {isTaskEdit = false;taskToEdit = null}"
|
||||||
:has-close="true"
|
:task="taskToEdit"
|
||||||
>
|
/>
|
||||||
<edit-task :task="taskToEdit"/>
|
|
||||||
</card>
|
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -349,7 +347,7 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let newTask = { ...taskDragged }
|
let newTask = {...taskDragged}
|
||||||
|
|
||||||
const didntHaveDates = newTask.startDate === null ? true : false
|
const didntHaveDates = newTask.startDate === null ? true : false
|
||||||
|
|
||||||
|
@ -447,12 +445,12 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$gantt-border: 1px solid $grey-200;
|
$gantt-border: 1px solid var(--grey-200);
|
||||||
$gantt-vertical-border-color: $grey-100;
|
$gantt-vertical-border-color: var(--grey-100);
|
||||||
|
|
||||||
.gantt-chart {
|
.gantt-chart {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
border-top: 1px solid $grey-200;
|
border-top: 1px solid var(--grey-200);
|
||||||
|
|
||||||
.dates {
|
.dates {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -479,8 +477,8 @@ $gantt-vertical-border-color: $grey-100;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
|
||||||
&.today {
|
&.today {
|
||||||
background: $primary;
|
background: var(--primary);
|
||||||
color: $white;
|
color: var(--white);
|
||||||
border-radius: 5px 5px 0 0;
|
border-radius: 5px 5px 0 0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
@ -502,7 +500,6 @@ $gantt-vertical-border-color: $grey-100;
|
||||||
|
|
||||||
.tasks {
|
.tasks {
|
||||||
max-width: unset !important;
|
max-width: unset !important;
|
||||||
margin: 0;
|
|
||||||
border-top: $gantt-border;
|
border-top: $gantt-border;
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
|
@ -510,7 +507,7 @@ $gantt-vertical-border-color: $grey-100;
|
||||||
|
|
||||||
.task {
|
.task {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 2px solid $primary;
|
border: 2px solid var(--primary);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
margin: 0.5rem;
|
margin: 0.5rem;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
@ -523,30 +520,30 @@ $gantt-vertical-border-color: $grey-100;
|
||||||
user-select: none; // Non-prefixed version
|
user-select: none; // Non-prefixed version
|
||||||
|
|
||||||
&.is-current-edit {
|
&.is-current-edit {
|
||||||
border-color: $orange !important;
|
border-color: var(--warning) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-light-text {
|
&.has-light-text {
|
||||||
color: $light;
|
color: var(--light);
|
||||||
|
|
||||||
&.done span:after {
|
&.done span:after {
|
||||||
border-top: 1px solid $light;
|
border-top: 1px solid var(--light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-toggle {
|
.edit-toggle {
|
||||||
color: $light;
|
color: var(--light);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-dark-text {
|
&.has-dark-text {
|
||||||
color: $text;
|
color: var(--text);
|
||||||
|
|
||||||
&.done span:after {
|
&.done span:after {
|
||||||
border-top: 1px solid $dark;
|
border-top: 1px solid var(--dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-toggle {
|
.edit-toggle {
|
||||||
color: $text;
|
color: var(--text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -599,8 +596,8 @@ $gantt-vertical-border-color: $grey-100;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.nodate {
|
&.nodate {
|
||||||
border: 2px dashed $grey-300;
|
border: 2px dashed var(--grey-300);
|
||||||
background: $grey-100;
|
background: var(--grey-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
|
@ -612,7 +609,6 @@ $gantt-vertical-border-color: $grey-100;
|
||||||
|
|
||||||
.taskedit {
|
.taskedit {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
min-height: 0;
|
|
||||||
top: 10vh;
|
top: 10vh;
|
||||||
right: 10vw;
|
right: 10vw;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import TaskCollectionService from '@/services/taskCollection'
|
import TaskCollectionService from '@/services/taskCollection'
|
||||||
|
|
||||||
// FIXME: merge with DEFAULT_PARAMS in filters.vue
|
// FIXME: merge with DEFAULT_PARAMS in filters.vue
|
||||||
const DEFAULT_PARAMS = {
|
export const getDefaultParams = () => ({
|
||||||
sort_by: ['position', 'id'],
|
sort_by: ['position', 'id'],
|
||||||
order_by: ['asc', 'desc'],
|
order_by: ['asc', 'desc'],
|
||||||
filter_by: ['done'],
|
filter_by: ['done'],
|
||||||
filter_value: ['false'],
|
filter_value: ['false'],
|
||||||
filter_comparator: ['equals'],
|
filter_comparator: ['equals'],
|
||||||
filter_concat: 'and',
|
filter_concat: 'and',
|
||||||
}
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This mixin provides a base set of methods and properties to get tasks on a list.
|
* This mixin provides a base set of methods and properties to get tasks on a list.
|
||||||
|
@ -26,7 +26,7 @@ export default {
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
|
|
||||||
showTaskFilter: false,
|
showTaskFilter: false,
|
||||||
params: DEFAULT_PARAMS,
|
params: {...getDefaultParams()},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -94,7 +94,7 @@ export default {
|
||||||
this.initTasks(page, search)
|
this.initTasks(page, search)
|
||||||
},
|
},
|
||||||
loadTasksOnSavedFilter() {
|
loadTasksOnSavedFilter() {
|
||||||
if(typeof this.$route.params.listId !== 'undefined' && parseInt(this.$route.params.listId) < 0) {
|
if (typeof this.$route.params.listId !== 'undefined' && parseInt(this.$route.params.listId) < 0) {
|
||||||
this.loadTasks(1, '', null, true)
|
this.loadTasks(1, '', null, true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -267,17 +267,17 @@ export default {
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $grey-200;
|
background-color: var(--grey-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
.filename {
|
.filename {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: .25rem;
|
margin-bottom: .25rem;
|
||||||
color: $text;
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
color: $grey-500;
|
color: var(--grey-500);
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
|
@ -339,17 +339,17 @@ export default {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 5rem;
|
font-size: 5rem;
|
||||||
height: auto;
|
height: auto;
|
||||||
text-shadow: $shadow-md;
|
text-shadow: var(--shadow-md);
|
||||||
animation: bounce 2s infinite;
|
animation: bounce 2s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hint {
|
.hint {
|
||||||
margin: .5rem auto 2rem;
|
margin: .5rem auto 2rem;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: $shadow-md;
|
box-shadow: var(--shadow-md);
|
||||||
background: $primary;
|
background: var(--primary);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
color: $white;
|
color: var(--white);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ export default {
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.checklist-summary {
|
.checklist-summary {
|
||||||
color: $grey-500;
|
color: var(--grey-500);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
@ -49,10 +49,10 @@ export default {
|
||||||
margin-right: .25rem;
|
margin-right: .25rem;
|
||||||
|
|
||||||
circle {
|
circle {
|
||||||
stroke: $grey-400;
|
stroke: var(--grey-400);
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
stroke: $primary;
|
stroke: var(--primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -276,7 +276,7 @@ export default {
|
||||||
|
|
||||||
this.commentEdit.taskId = this.taskId
|
this.commentEdit.taskId = this.taskId
|
||||||
try {
|
try {
|
||||||
const comment = this.taskCommentService.update(this.commentEdit)
|
const comment = await this.taskCommentService.update(this.commentEdit)
|
||||||
for (const c in this.comments) {
|
for (const c in this.comments) {
|
||||||
if (this.comments[c].id === this.commentEdit.id) {
|
if (this.comments[c].id === this.commentEdit.id) {
|
||||||
this.comments[c] = comment
|
this.comments[c] = comment
|
||||||
|
@ -306,24 +306,16 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.media.comment {
|
.media-left {
|
||||||
align-items: center;
|
margin: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.media-left {
|
.comment-info {
|
||||||
margin: 0 1rem;
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
|
gap: .5rem;
|
||||||
.comment-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
* {
|
|
||||||
padding-right: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
|
img {
|
||||||
@media screen and (max-width: $tablet) {
|
@media screen and (max-width: $tablet) {
|
||||||
display: block;
|
display: block;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
@ -331,22 +323,20 @@ export default {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
margin-right: .5rem;
|
margin-right: .5rem;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@media screen and (min-width: $tablet) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.editor {
|
|
||||||
margin-top: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-content {
|
.media-content {
|
||||||
width: calc(100% - 48px - 2rem);
|
width: calc(100% - 48px - 2rem);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -141,14 +141,14 @@ $defer-task-max-width: 350px + 100px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: $defer-task-max-width;
|
max-width: $defer-task-max-width;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
border: 1px solid $grey-200;
|
border: 1px solid var(--grey-200);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
background: $white;
|
background: var(--white);
|
||||||
color: $text;
|
color: var(--text);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
box-shadow: $shadow-lg;
|
box-shadow: var(--shadow-lg);
|
||||||
|
|
||||||
@media screen and (max-width: ($defer-task-max-width)) {
|
@media screen and (max-width: ($defer-task-max-width)) {
|
||||||
left: .5rem;
|
left: .5rem;
|
||||||
|
|
|
@ -127,7 +127,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.user img) {
|
:deep(.user img) {
|
||||||
border: 2px solid $white;
|
border: 2px solid var(--white);
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,8 +135,8 @@ export default {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 4px;
|
top: 4px;
|
||||||
left: 2px;
|
left: 2px;
|
||||||
color: $red;
|
color: var(--danger);
|
||||||
background: $white;
|
background: var(--white);
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
:class="{'disabled': !canWrite}"
|
:class="{'disabled': !canWrite}"
|
||||||
@blur="save($event.target.textContent)"
|
@blur="save($event.target.textContent)"
|
||||||
@keydown.enter.prevent.stop="$event.target.blur()"
|
@keydown.enter.prevent.stop="$event.target.blur()"
|
||||||
:contenteditable="canWrite ? 'true' : 'false'"
|
:contenteditable="canWrite ? true : undefined"
|
||||||
:spellcheck="false"
|
:spellcheck="false"
|
||||||
>
|
>
|
||||||
{{ task.title.trim() }}
|
{{ task.title.trim() }}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
class="task loader-container draggable"
|
||||||
:class="{
|
:class="{
|
||||||
'is-loading': loadingInternal || loading,
|
'is-loading': loadingInternal || loading,
|
||||||
'draggable': !(loadingInternal || loading),
|
'draggable': !(loadingInternal || loading),
|
||||||
|
@ -9,7 +10,6 @@
|
||||||
@click.ctrl="() => toggleTaskDone(task)"
|
@click.ctrl="() => toggleTaskDone(task)"
|
||||||
@click.exact="() => $router.push({ name: 'task.kanban.detail', params: { id: task.id } })"
|
@click.exact="() => $router.push({ name: 'task.kanban.detail', params: { id: task.id } })"
|
||||||
@click.meta="() => toggleTaskDone(task)"
|
@click.meta="() => toggleTaskDone(task)"
|
||||||
class="task loader-container draggable"
|
|
||||||
>
|
>
|
||||||
<span class="task-id">
|
<span class="task-id">
|
||||||
<Done class="kanban-card__done" :is-done="task.done" variant="small" />
|
<Done class="kanban-card__done" :is-done="task.done" variant="small" />
|
||||||
|
@ -117,13 +117,13 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$task-background: $white;
|
$task-background: var(--white);
|
||||||
|
|
||||||
.task {
|
.task {
|
||||||
-webkit-touch-callout: none; // iOS Safari
|
-webkit-touch-callout: none; // iOS Safari
|
||||||
user-select: none;
|
user-select: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: $shadow-xs;
|
box-shadow: var(--shadow-xs);
|
||||||
display: block;
|
display: block;
|
||||||
border: 3px solid transparent;
|
border: 3px solid transparent;
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ $task-background: $white;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.overdue {
|
&.overdue {
|
||||||
color: $red;
|
color: var(--danger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +219,7 @@ $task-background: $white;
|
||||||
.footer .icon,
|
.footer .icon,
|
||||||
.due-date,
|
.due-date,
|
||||||
.priority-label {
|
.priority-label {
|
||||||
background: $grey-100;
|
background: var(--grey-100);
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
padding: 0 .5rem;
|
padding: 0 .5rem;
|
||||||
}
|
}
|
||||||
|
@ -229,7 +229,7 @@ $task-background: $white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-id {
|
.task-id {
|
||||||
color: $grey-500;
|
color: var(--grey-500);
|
||||||
font-size: .8rem;
|
font-size: .8rem;
|
||||||
margin-bottom: .25rem;
|
margin-bottom: .25rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -244,21 +244,21 @@ $task-background: $white;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-light-text {
|
&.has-light-text {
|
||||||
color: $white;
|
color: var(--white);
|
||||||
|
|
||||||
.task-id {
|
.task-id {
|
||||||
color: $grey-200;
|
color: var(--grey-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer .icon,
|
.footer .icon,
|
||||||
.due-date,
|
.due-date,
|
||||||
.priority-label {
|
.priority-label {
|
||||||
background: $grey-800;
|
background: var(--grey-800);
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
.icon svg {
|
.icon svg {
|
||||||
fill: $white;
|
fill: var(--white);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<multiselect
|
<multiselect
|
||||||
class="control is-expanded"
|
class="control is-expanded"
|
||||||
:loading="listSerivce.loading"
|
|
||||||
:placeholder="$t('list.search')"
|
:placeholder="$t('list.search')"
|
||||||
@search="findLists"
|
@search="findLists"
|
||||||
:search-results="foundLists"
|
:search-results="foundLists"
|
||||||
|
@ -18,7 +17,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ListService from '../../../services/list'
|
|
||||||
import ListModel from '../../../models/list'
|
import ListModel from '../../../models/list'
|
||||||
import Multiselect from '@/components/input/multiselect.vue'
|
import Multiselect from '@/components/input/multiselect.vue'
|
||||||
|
|
||||||
|
@ -26,7 +24,6 @@ export default {
|
||||||
name: 'listSearch',
|
name: 'listSearch',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
listSerivce: new ListService(),
|
|
||||||
list: new ListModel(),
|
list: new ListModel(),
|
||||||
foundLists: [],
|
foundLists: [],
|
||||||
}
|
}
|
||||||
|
@ -50,17 +47,8 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async findLists(query) {
|
findLists(query) {
|
||||||
if (query === '') {
|
this.foundLists = this.$store.getters['lists/searchList'](query)
|
||||||
this.clearAll()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.foundLists = await this.listSerivce.getAll({}, {s: query})
|
|
||||||
},
|
|
||||||
|
|
||||||
clearAll() {
|
|
||||||
this.foundLists = []
|
|
||||||
},
|
},
|
||||||
|
|
||||||
select(list) {
|
select(list) {
|
||||||
|
@ -82,6 +70,6 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.list-namespace-title {
|
.list-namespace-title {
|
||||||
color: $grey-500;
|
color: var(--grey-500);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -54,7 +54,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
span.high-priority {
|
span.high-priority {
|
||||||
color: $red;
|
color: var(--danger);
|
||||||
width: auto !important; // To override the width set in tasks
|
width: auto !important; // To override the width set in tasks
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
@ -64,7 +64,7 @@ span.high-priority {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.not-so-high {
|
&.not-so-high {
|
||||||
color: $orange;
|
color: var(--warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -11,7 +11,7 @@
|
||||||
:overflow="true"
|
:overflow="true"
|
||||||
variant="hint-modal"
|
variant="hint-modal"
|
||||||
>
|
>
|
||||||
<card class="has-background-white has-no-shadow" :title="$t('task.quickAddMagic.title')">
|
<card class="has-no-shadow" :title="$t('task.quickAddMagic.title')">
|
||||||
<p>{{ $t('task.quickAddMagic.intro') }}</p>
|
<p>{{ $t('task.quickAddMagic.intro') }}</p>
|
||||||
|
|
||||||
<h3>{{ $t('task.attributes.labels') }}</h3>
|
<h3>{{ $t('task.attributes.labels') }}</h3>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
:placeholder="$t('task.relation.searchPlaceholder')"
|
:placeholder="$t('task.relation.searchPlaceholder')"
|
||||||
@search="findTasks"
|
@search="findTasks"
|
||||||
:loading="taskService.loading"
|
:loading="taskService.loading"
|
||||||
:search-results="foundTasks"
|
:search-results="mappedFoundTasks"
|
||||||
label="title"
|
label="title"
|
||||||
v-model="newTaskRelationTask"
|
v-model="newTaskRelationTask"
|
||||||
:creatable="true"
|
:creatable="true"
|
||||||
|
@ -41,8 +41,17 @@
|
||||||
<span
|
<span
|
||||||
class="different-list"
|
class="different-list"
|
||||||
v-if="props.option.listId !== listId"
|
v-if="props.option.listId !== listId"
|
||||||
v-tooltip="$t('task.relation.differentList')">
|
>
|
||||||
{{ $store.getters['lists/getListById'](props.option.listId) === null ? '' : $store.getters['lists/getListById'](props.option.listId).title }} >
|
<span
|
||||||
|
v-if="props.option.differentNamespace !== null"
|
||||||
|
v-tooltip="$t('task.relation.differentNamespace')">
|
||||||
|
{{ props.option.differentNamespace }} >
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="props.option.differentList !== null"
|
||||||
|
v-tooltip="$t('task.relation.differentList')">
|
||||||
|
{{ props.option.differentList }} >
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{{ props.option.title }}
|
{{ props.option.title }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -70,33 +79,36 @@
|
||||||
</template>
|
</template>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
|
|
||||||
<div :key="kind" class="related-tasks" v-for="(rts, kind ) in relatedTasks">
|
<div :key="rts.kind" class="related-tasks" v-for="rts in mappedRelatedTasks">
|
||||||
<template v-if="rts.length > 0">
|
<span class="title">{{ rts.title }}</span>
|
||||||
<span class="title">{{ relationKindTitle(kind, rts.length) }}</span>
|
<div class="tasks">
|
||||||
<div class="tasks noborder">
|
<div :key="t.id" class="task" v-for="t in rts.tasks">
|
||||||
<div :key="t.id" class="task" v-for="t in rts.filter(t => t)">
|
<router-link :to="{ name: $route.name, params: { id: t.id } }" :class="{ 'done': t.done}">
|
||||||
<router-link :to="{ name: $route.name, params: { id: t.id } }">
|
<span
|
||||||
<span :class="{ 'done': t.done}" class="tasktext">
|
class="different-list"
|
||||||
<span
|
v-if="t.listId !== listId"
|
||||||
class="different-list"
|
>
|
||||||
v-if="t.listId !== listId"
|
<span
|
||||||
v-tooltip="$t('task.relation.differentList')">
|
v-if="t.differentNamespace !== null"
|
||||||
{{
|
v-tooltip="$t('task.relation.differentNamespace')">
|
||||||
$store.getters['lists/getListById'](t.listId) === null ? '' : $store.getters['lists/getListById'](t.listId).title
|
{{ t.differentNamespace }} >
|
||||||
}} >
|
|
||||||
</span>
|
|
||||||
{{ t.title }}
|
|
||||||
</span>
|
</span>
|
||||||
</router-link>
|
<span
|
||||||
<a
|
v-if="t.differentList !== null"
|
||||||
@click="() => {showDeleteModal = true; relationToDelete = {relationKind: kind, otherTaskId: t.id}}"
|
v-tooltip="$t('task.relation.differentList')">
|
||||||
class="remove"
|
{{ t.differentList }} >
|
||||||
v-if="editEnabled">
|
</span>
|
||||||
<icon icon="trash-alt"/>
|
</span>
|
||||||
</a>
|
{{ t.title }}
|
||||||
</div>
|
</router-link>
|
||||||
|
<a
|
||||||
|
@click="() => {showDeleteModal = true; relationToDelete = {relationKind: rts.kind, otherTaskId: t.id}}"
|
||||||
|
class="remove"
|
||||||
|
v-if="editEnabled">
|
||||||
|
<icon icon="trash-alt"/>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="none" v-if="showNoRelationsNotice && Object.keys(relatedTasks).length === 0">
|
<p class="none" v-if="showNoRelationsNotice && Object.keys(relatedTasks).length === 0">
|
||||||
{{ $t('task.relation.noneYet') }}
|
{{ $t('task.relation.noneYet') }}
|
||||||
|
@ -110,10 +122,10 @@
|
||||||
v-if="showDeleteModal"
|
v-if="showDeleteModal"
|
||||||
>
|
>
|
||||||
<template #header><span>{{ $t('task.relation.delete') }}</span></template>
|
<template #header><span>{{ $t('task.relation.delete') }}</span></template>
|
||||||
|
|
||||||
<template #text>
|
<template #text>
|
||||||
<p>{{ $t('task.relation.deleteText1') }}<br/>
|
<p>{{ $t('task.relation.deleteText1') }}<br/>
|
||||||
<strong>{{ $t('task.relation.deleteText2') }}</strong></p>
|
<strong>{{ $t('task.relation.deleteText2') }}</strong></p>
|
||||||
</template>
|
</template>
|
||||||
</modal>
|
</modal>
|
||||||
</transition>
|
</transition>
|
||||||
|
@ -183,6 +195,19 @@ export default {
|
||||||
showCreate() {
|
showCreate() {
|
||||||
return Object.keys(this.relatedTasks).length === 0 || this.showNewRelationForm
|
return Object.keys(this.relatedTasks).length === 0 || this.showNewRelationForm
|
||||||
},
|
},
|
||||||
|
namespace() {
|
||||||
|
return this.$store.getters['namespaces/getListAndNamespaceById'](this.listId, true)?.namespace
|
||||||
|
},
|
||||||
|
mappedRelatedTasks() {
|
||||||
|
return Object.entries(this.relatedTasks).map(([kind, tasks]) => ({
|
||||||
|
title: this.$tc(`task.relation.kinds.${kind}`, tasks.length),
|
||||||
|
tasks: this.mapRelatedTasks(tasks),
|
||||||
|
kind,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
mappedFoundTasks() {
|
||||||
|
return this.mapRelatedTasks(this.foundTasks.filter(t => t.id !== this.taskId))
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async findTasks(query) {
|
async findTasks(query) {
|
||||||
|
@ -217,15 +242,14 @@ export default {
|
||||||
try {
|
try {
|
||||||
await this.taskRelationService.delete(rel)
|
await this.taskRelationService.delete(rel)
|
||||||
|
|
||||||
Object.entries(this.relatedTasks).some(([relationKind, t]) => {
|
const kind = this.relationToDelete.relationKind
|
||||||
const found = typeof this.relatedTasks[relationKind][t] !== 'undefined' &&
|
for (const t in this.relatedTasks[kind]) {
|
||||||
this.relatedTasks[relationKind][t].id === this.relationToDelete.otherTaskId &&
|
if (this.relatedTasks[kind][t].id === this.relationToDelete.otherTaskId) {
|
||||||
relationKind === this.relationToDelete.relationKind
|
this.relatedTasks[kind].splice(t, 1)
|
||||||
if (!found) return false
|
|
||||||
|
|
||||||
this.relatedTasks[relationKind].splice(t, 1)
|
break
|
||||||
return true
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
this.saved = true
|
this.saved = true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -245,13 +269,34 @@ export default {
|
||||||
relationKindTitle(kind, length) {
|
relationKindTitle(kind, length) {
|
||||||
return this.$tc(`task.relation.kinds.${kind}`, length)
|
return this.$tc(`task.relation.kinds.${kind}`, length)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mapRelatedTasks(tasks) {
|
||||||
|
return tasks
|
||||||
|
.map(task => {
|
||||||
|
// by doing this here once we can save a lot of duplicate calls in the template
|
||||||
|
const {
|
||||||
|
list,
|
||||||
|
namespace,
|
||||||
|
} = this.$store.getters['namespaces/getListAndNamespaceById'](task.listId, true)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...task,
|
||||||
|
differentNamespace:
|
||||||
|
(namespace !== null &&
|
||||||
|
namespace.id !== this.namespace.id &&
|
||||||
|
namespace?.title) || null,
|
||||||
|
differentList:
|
||||||
|
(list !== null &&
|
||||||
|
task.listId !== this.listId &&
|
||||||
|
list?.title) || null,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$remove-icon-width: 24px;
|
|
||||||
|
|
||||||
.add-task-relation-button {
|
.add-task-relation-button {
|
||||||
margin-top: -3rem;
|
margin-top: -3rem;
|
||||||
|
|
||||||
|
@ -264,71 +309,59 @@ $remove-icon-width: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-relations {
|
.different-list {
|
||||||
&.is-narrow .columns {
|
color: var(--grey-500);
|
||||||
display: block;
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.column {
|
.title {
|
||||||
width: 100%;
|
font-size: 1rem;
|
||||||
}
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.different-list {
|
.tasks {
|
||||||
color: $grey-500;
|
padding: .5rem;
|
||||||
width: auto;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.related-tasks {
|
.task {
|
||||||
.title {
|
display: flex;
|
||||||
font-size: 1rem;
|
flex-wrap: wrap;
|
||||||
margin: 0;
|
justify-content: space-between;
|
||||||
}
|
padding: .75rem;
|
||||||
|
transition: background-color $transition;
|
||||||
|
border-radius: $radius;
|
||||||
|
|
||||||
.tasks {
|
&:hover {
|
||||||
margin: 0;
|
background-color: var(--grey-200);
|
||||||
|
}
|
||||||
|
|
||||||
a:not(.remove) {
|
a {
|
||||||
width: calc(100% - #{$remove-icon-width});
|
color: var(--text);
|
||||||
}
|
transition: color ease $transition-duration;
|
||||||
|
|
||||||
.task .tasktext {
|
&:hover {
|
||||||
width: calc(100% - .25rem); // Magic .25rem extra space
|
color: var(--grey-900);
|
||||||
}
|
|
||||||
|
|
||||||
.remove {
|
|
||||||
width: $remove-icon-width;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.task {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: .4rem;
|
|
||||||
transition: background-color $transition;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: $radius;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: $text;
|
|
||||||
transition: color ease $transition-duration;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $grey-900;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove {
|
|
||||||
color: $red;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.none {
|
.remove {
|
||||||
font-style: italic;
|
text-align: center;
|
||||||
text-align: center;
|
color: var(--danger);
|
||||||
}
|
opacity: 0;
|
||||||
|
transition: opacity $transition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-tasks:hover .tasks .task .remove {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.none {
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.multiselect .search-results button) {
|
||||||
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -112,7 +112,7 @@ export default {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&.overdue :deep(.datepicker a.show) {
|
&.overdue :deep(.datepicker a.show) {
|
||||||
color: $red;
|
color: var(--danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
@ -120,7 +120,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
a.remove {
|
a.remove {
|
||||||
color: $red;
|
color: var(--danger);
|
||||||
padding-left: .5rem;
|
padding-left: .5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -227,7 +227,7 @@ export default {
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $grey-100;
|
background-color: var(--grey-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tasktext,
|
.tasktext,
|
||||||
|
@ -239,13 +239,13 @@ export default {
|
||||||
flex: 1 0 50%;
|
flex: 1 0 50%;
|
||||||
|
|
||||||
.overdue {
|
.overdue {
|
||||||
color: $red;
|
color: var(--danger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list {
|
.task-list {
|
||||||
width: auto;
|
width: auto;
|
||||||
color: $grey-400;
|
color: var(--grey-400);
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
@ -273,11 +273,11 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $text;
|
color: var(--text);
|
||||||
transition: color ease $transition-duration;
|
transition: color ease $transition-duration;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $grey-900;
|
color: var(--grey-900);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,12 +288,12 @@ export default {
|
||||||
transition: opacity $transition, color $transition;
|
transition: opacity $transition, color $transition;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $orange;
|
color: var(--warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-favorite {
|
&.is-favorite {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
color: $orange;
|
color: var(--warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,16 +324,16 @@ export default {
|
||||||
|
|
||||||
.tasktext.done {
|
.tasktext.done {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
color: $grey-500;
|
color: var(--grey-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
span.parent-tasks {
|
span.parent-tasks {
|
||||||
color: $grey-500;
|
color: var(--grey-500);
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.remove {
|
.remove {
|
||||||
color: $red;
|
color: var(--danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
|
@ -351,8 +351,8 @@ export default {
|
||||||
left: calc(50% - 1rem);
|
left: calc(50% - 1rem);
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
border-left-color: $grey-300;
|
border-left-color: var(--grey-300);
|
||||||
border-bottom-color: $grey-300;
|
border-bottom-color: var(--grey-300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,48 @@
|
||||||
|
import {computed, watch, readonly} from 'vue'
|
||||||
|
import {useStorage, createSharedComposable, ColorSchemes, usePreferredColorScheme, tryOnMounted} from '@vueuse/core'
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'color-scheme'
|
||||||
|
|
||||||
|
const DEFAULT_COLOR_SCHEME_SETTING: ColorSchemes = 'light'
|
||||||
|
|
||||||
|
const CLASS_DARK = 'dark'
|
||||||
|
const CLASS_LIGHT = 'light'
|
||||||
|
|
||||||
|
// This is built upon the vueuse useDark
|
||||||
|
// Main differences:
|
||||||
|
// - usePreferredColorScheme
|
||||||
|
// - doesn't allow setting via the `isDark` ref.
|
||||||
|
// - instead the store is exposed
|
||||||
|
// - value is synced via `createSharedComposable`
|
||||||
|
// https://github.com/vueuse/vueuse/blob/main/packages/core/useDark/index.ts
|
||||||
|
export const useColorScheme = createSharedComposable(() => {
|
||||||
|
const store = useStorage<ColorSchemes>(STORAGE_KEY, DEFAULT_COLOR_SCHEME_SETTING)
|
||||||
|
|
||||||
|
const preferredColorScheme = usePreferredColorScheme()
|
||||||
|
|
||||||
|
const isDark = computed<boolean>(() => {
|
||||||
|
if (store.value !== 'auto') {
|
||||||
|
return store.value === 'dark'
|
||||||
|
}
|
||||||
|
|
||||||
|
const autoColorScheme = preferredColorScheme.value === 'no-preference'
|
||||||
|
? DEFAULT_COLOR_SCHEME_SETTING
|
||||||
|
: preferredColorScheme.value
|
||||||
|
return autoColorScheme === 'dark'
|
||||||
|
})
|
||||||
|
|
||||||
|
function onChanged(v: boolean) {
|
||||||
|
const el = window?.document.querySelector('html')
|
||||||
|
el?.classList.toggle(CLASS_DARK, v)
|
||||||
|
el?.classList.toggle(CLASS_LIGHT, !v)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(isDark, onChanged, { flush: 'post' })
|
||||||
|
|
||||||
|
tryOnMounted(() => onChanged(isDark.value))
|
||||||
|
|
||||||
|
return {
|
||||||
|
store,
|
||||||
|
isDark: readonly(isDark),
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { computed, watchEffect } from 'vue'
|
||||||
|
import { setTitle } from '@/helpers/setTitle'
|
||||||
|
|
||||||
|
import { ComputedGetter, ComputedRef } from '@vue/reactivity'
|
||||||
|
|
||||||
|
export function useTitle<T>(titleGetter: ComputedGetter<T>) : ComputedRef<T> {
|
||||||
|
const titleRef = computed(titleGetter)
|
||||||
|
|
||||||
|
watchEffect(() => setTitle(titleRef.value))
|
||||||
|
|
||||||
|
return titleRef
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import {Directive} from 'vue'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
Cypress: object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cypressDirective: Directive = {
|
||||||
|
mounted(el, {value}) {
|
||||||
|
if (
|
||||||
|
(window.Cypress || import.meta.env.DEV) &&
|
||||||
|
value
|
||||||
|
) {
|
||||||
|
el.setAttribute('data-cy', value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeUnmount(el) {
|
||||||
|
el.removeAttribute('data-cy')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default cypressDirective
|
|
@ -0,0 +1,17 @@
|
||||||
|
import {Directive} from 'vue'
|
||||||
|
import {install, uninstall} from '@github/hotkey'
|
||||||
|
import {isAppleDevice} from '@/helpers/isAppleDevice'
|
||||||
|
|
||||||
|
const directive: Directive = {
|
||||||
|
mounted(el, {value}) {
|
||||||
|
if (isAppleDevice() && value.includes('Control')) {
|
||||||
|
value = value.replace('Control', 'Meta')
|
||||||
|
}
|
||||||
|
install(el, value)
|
||||||
|
},
|
||||||
|
beforeUnmount(el) {
|
||||||
|
uninstall(el)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default directive
|
|
@ -1,83 +0,0 @@
|
||||||
const calculateTop = (coords, tooltip) => {
|
|
||||||
// Bottom tooltip use the exact inverse calculation compared to the default.
|
|
||||||
if (tooltip.classList.contains('bottom')) {
|
|
||||||
return coords.top + tooltip.offsetHeight + 5
|
|
||||||
}
|
|
||||||
|
|
||||||
// The top position of the tooltip is the coordinates of the bound element - the height of the tooltip -
|
|
||||||
// 5px spacing for the arrow (which is exactly 5px high)
|
|
||||||
return coords.top - tooltip.offsetHeight - 5
|
|
||||||
}
|
|
||||||
|
|
||||||
const calculateArrowTop = (top, tooltip) => {
|
|
||||||
if (tooltip.classList.contains('bottom')) {
|
|
||||||
return `${top - 5}px` // 5px arrow height
|
|
||||||
}
|
|
||||||
return `${top + tooltip.offsetHeight}px`
|
|
||||||
}
|
|
||||||
|
|
||||||
// This global object holds all created tooltip elements (and their arrows) using the element they were created for as
|
|
||||||
// key. This allows us to find the tooltip elements if the element the tooltip was created for is unbound so that
|
|
||||||
// we can remove the tooltip element.
|
|
||||||
const createdTooltips = {}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mounted(el, {value, modifiers}) {
|
|
||||||
// First, we create the tooltip and arrow elements
|
|
||||||
const tooltip = document.createElement('div')
|
|
||||||
tooltip.style.position = 'fixed'
|
|
||||||
tooltip.innerText = value
|
|
||||||
tooltip.classList.add('tooltip')
|
|
||||||
const arrow = document.createElement('div')
|
|
||||||
arrow.classList.add('tooltip-arrow')
|
|
||||||
arrow.style.position = 'fixed'
|
|
||||||
|
|
||||||
if (typeof modifiers.bottom !== 'undefined') {
|
|
||||||
tooltip.classList.add('bottom')
|
|
||||||
arrow.classList.add('bottom')
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't append the element until hovering over it because that's the most reliable way to determine
|
|
||||||
// where the parent elemtent is located at the time the user hovers over it.
|
|
||||||
el.addEventListener('mouseover', () => {
|
|
||||||
// Appending the element right away because we can only calculate the height of the element if it is
|
|
||||||
// already in the DOM.
|
|
||||||
document.body.appendChild(tooltip)
|
|
||||||
document.body.appendChild(arrow)
|
|
||||||
|
|
||||||
const coords = el.getBoundingClientRect()
|
|
||||||
const top = calculateTop(coords, tooltip)
|
|
||||||
// The left position of the tooltip is calculated so that the middle point of the tooltip
|
|
||||||
// (where the arrow will be) is the middle of the bound element
|
|
||||||
const left = coords.left - (tooltip.offsetWidth / 2) + (el.offsetWidth / 2)
|
|
||||||
// Now setting all the values
|
|
||||||
tooltip.style.top = `${top}px`
|
|
||||||
tooltip.style.left = `${coords.left}px`
|
|
||||||
tooltip.style.left = `${left}px`
|
|
||||||
|
|
||||||
arrow.style.left = `${left + (tooltip.offsetWidth / 2) - (arrow.offsetWidth / 2)}px`
|
|
||||||
arrow.style.top = calculateArrowTop(top, tooltip)
|
|
||||||
|
|
||||||
// And finally make it visible to the user. This will also trigger a nice fade-in animation through
|
|
||||||
// css transitions
|
|
||||||
tooltip.classList.add('visible')
|
|
||||||
arrow.classList.add('visible')
|
|
||||||
})
|
|
||||||
|
|
||||||
el.addEventListener('mouseout', () => {
|
|
||||||
tooltip.classList.remove('visible')
|
|
||||||
arrow.classList.remove('visible')
|
|
||||||
})
|
|
||||||
|
|
||||||
createdTooltips[el] = {
|
|
||||||
tooltip: tooltip,
|
|
||||||
arrow: arrow,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
unmounted(el) {
|
|
||||||
if (typeof createdTooltips[el] !== 'undefined') {
|
|
||||||
createdTooltips[el].tooltip.remove()
|
|
||||||
createdTooltips[el].arrow.remove()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
import {store} from '@/store'
|
||||||
|
|
||||||
|
const API_DEFAULT_PORT = '3456'
|
||||||
|
|
||||||
|
export const ERROR_NO_API_URL = 'noApiUrlProvided'
|
||||||
|
|
||||||
|
const updateConfig = () => store.dispatch('config/update')
|
||||||
|
|
||||||
|
export const checkAndSetApiUrl = (url: string): Promise<string> => {
|
||||||
|
if(url.startsWith('/')) {
|
||||||
|
url = window.location.host + url
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the url has an http prefix
|
||||||
|
if (
|
||||||
|
!url.startsWith('http://') &&
|
||||||
|
!url.startsWith('https://')
|
||||||
|
) {
|
||||||
|
url = `http://${url}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlToCheck: URL = new URL(url)
|
||||||
|
const origUrlToCheck = urlToCheck
|
||||||
|
|
||||||
|
const oldUrl = window.API_URL
|
||||||
|
window.API_URL = urlToCheck.toString()
|
||||||
|
|
||||||
|
// Check if the api is reachable at the provided url
|
||||||
|
return updateConfig()
|
||||||
|
.catch(e => {
|
||||||
|
// Check if it is reachable at /api/v1 and http
|
||||||
|
if (
|
||||||
|
!urlToCheck.pathname.endsWith('/api/v1') &&
|
||||||
|
!urlToCheck.pathname.endsWith('/api/v1/')
|
||||||
|
) {
|
||||||
|
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
|
||||||
|
window.API_URL = urlToCheck.toString()
|
||||||
|
return updateConfig()
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
// Check if it has a port and if not check if it is reachable at https
|
||||||
|
if (urlToCheck.protocol === 'http:') {
|
||||||
|
urlToCheck.protocol = 'https:'
|
||||||
|
window.API_URL = urlToCheck.toString()
|
||||||
|
return updateConfig()
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
// Check if it is reachable at /api/v1 and https
|
||||||
|
urlToCheck.pathname = origUrlToCheck.pathname
|
||||||
|
if (
|
||||||
|
!urlToCheck.pathname.endsWith('/api/v1') &&
|
||||||
|
!urlToCheck.pathname.endsWith('/api/v1/')
|
||||||
|
) {
|
||||||
|
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
|
||||||
|
window.API_URL = urlToCheck.toString()
|
||||||
|
return updateConfig()
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
// Check if it is reachable at port API_DEFAULT_PORT and https
|
||||||
|
if (urlToCheck.port !== API_DEFAULT_PORT) {
|
||||||
|
urlToCheck.protocol = 'https:'
|
||||||
|
urlToCheck.port = API_DEFAULT_PORT
|
||||||
|
window.API_URL = urlToCheck.toString()
|
||||||
|
return updateConfig()
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1 and https
|
||||||
|
urlToCheck.pathname = origUrlToCheck.pathname
|
||||||
|
if (
|
||||||
|
!urlToCheck.pathname.endsWith('/api/v1') &&
|
||||||
|
!urlToCheck.pathname.endsWith('/api/v1/')
|
||||||
|
) {
|
||||||
|
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
|
||||||
|
window.API_URL = urlToCheck.toString()
|
||||||
|
return updateConfig()
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
// Check if it is reachable at port API_DEFAULT_PORT and http
|
||||||
|
if (urlToCheck.port !== API_DEFAULT_PORT) {
|
||||||
|
urlToCheck.protocol = 'http:'
|
||||||
|
urlToCheck.port = API_DEFAULT_PORT
|
||||||
|
window.API_URL = urlToCheck.toString()
|
||||||
|
return updateConfig()
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1 and http
|
||||||
|
urlToCheck.pathname = origUrlToCheck.pathname
|
||||||
|
if (
|
||||||
|
!urlToCheck.pathname.endsWith('/api/v1') &&
|
||||||
|
!urlToCheck.pathname.endsWith('/api/v1/')
|
||||||
|
) {
|
||||||
|
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
|
||||||
|
window.API_URL = urlToCheck.toString()
|
||||||
|
return updateConfig()
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
window.API_URL = oldUrl
|
||||||
|
throw e
|
||||||
|
})
|
||||||
|
.then(r => {
|
||||||
|
if (typeof r !== 'undefined') {
|
||||||
|
localStorage.setItem('API_URL', window.API_URL)
|
||||||
|
return window.API_URL
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(ERROR_NO_API_URL)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
export const isAppleDevice = (): Boolean => {
|
||||||
|
return navigator.userAgent.includes('Mac') || [
|
||||||
|
'iPad Simulator',
|
||||||
|
'iPhone Simulator',
|
||||||
|
'iPod Simulator',
|
||||||
|
'iPad',
|
||||||
|
'iPhone',
|
||||||
|
'iPod',
|
||||||
|
].includes(navigator.platform)
|
||||||
|
}
|
|
@ -1,20 +1,25 @@
|
||||||
import {filterLabelsByQuery} from './labels'
|
import {filterLabelsByQuery} from './labels'
|
||||||
|
import {createNewIndexer} from '../indexes'
|
||||||
|
|
||||||
|
const {add} = createNewIndexer('labels', ['title', 'description'])
|
||||||
|
|
||||||
describe('filter labels', () => {
|
describe('filter labels', () => {
|
||||||
const state = {
|
const state = {
|
||||||
labels: [
|
labels: {
|
||||||
{id: 1, title: 'label1'},
|
1: {id: 1, title: 'label1'},
|
||||||
{id: 2, title: 'label2'},
|
2: {id: 2, title: 'label2'},
|
||||||
{id: 3, title: 'label3'},
|
3: {id: 3, title: 'label3'},
|
||||||
{id: 4, title: 'label4'},
|
4: {id: 4, title: 'label4'},
|
||||||
{id: 5, title: 'label5'},
|
5: {id: 5, title: 'label5'},
|
||||||
{id: 6, title: 'label6'},
|
6: {id: 6, title: 'label6'},
|
||||||
{id: 7, title: 'label7'},
|
7: {id: 7, title: 'label7'},
|
||||||
{id: 8, title: 'label8'},
|
8: {id: 8, title: 'label8'},
|
||||||
{id: 9, title: 'label9'},
|
9: {id: 9, title: 'label9'},
|
||||||
],
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object.values(state.labels).forEach(add)
|
||||||
|
|
||||||
it('should return an empty array for an empty query', () => {
|
it('should return an empty array for an empty query', () => {
|
||||||
const labels = filterLabelsByQuery(state, [], '')
|
const labels = filterLabelsByQuery(state, [], '')
|
||||||
|
|
||||||
|
@ -31,7 +36,7 @@ describe('filter labels', () => {
|
||||||
id: number,
|
id: number,
|
||||||
title: string,
|
title: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelsToHide: label[] = [{id: 1, title: 'label1'}]
|
const labelsToHide: label[] = [{id: 1, title: 'label1'}]
|
||||||
const labels = filterLabelsByQuery(state, labelsToHide, 'label1')
|
const labels = filterLabelsByQuery(state, labelsToHide, 'label1')
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
interface label {
|
import {createNewIndexer} from '../indexes'
|
||||||
|
|
||||||
|
const {search} = createNewIndexer('labels', ['title', 'description'])
|
||||||
|
|
||||||
|
export interface label {
|
||||||
id: number,
|
id: number,
|
||||||
title: string,
|
title: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface labelState {
|
interface labelState {
|
||||||
labels: label[],
|
labels: {
|
||||||
|
[k: number]: label,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,17 +21,12 @@ interface labelState {
|
||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
export function filterLabelsByQuery(state: labelState, labelsToHide: label[], query: string) {
|
export function filterLabelsByQuery(state: labelState, labelsToHide: label[], query: string) {
|
||||||
if (query === '') {
|
const labelIdsToHide: number[] = labelsToHide.map(({id}) => id)
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const labelQuery = query.toLowerCase()
|
return search(query)
|
||||||
const labelIds = labelsToHide.map(({id}) => id)
|
?.filter(value => !labelIdsToHide.includes(value))
|
||||||
return Object
|
.map(id => state.labels[id])
|
||||||
.values(state.labels)
|
|| []
|
||||||
.filter(({id, title}) => {
|
|
||||||
return !labelIds.includes(id) && title.toLowerCase().includes(labelQuery)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
export const setTitle = title => {
|
export function setTitle(title) {
|
||||||
if (typeof title === 'undefined' || title === '') {
|
document.title = (typeof title === 'undefined' || title === '')
|
||||||
document.title = 'Vikunja'
|
? 'Vikunja'
|
||||||
return
|
: `${title} | Vikunja`
|
||||||
}
|
|
||||||
|
|
||||||
document.title = `${title} | Vikunja`
|
|
||||||
}
|
}
|
|
@ -16,6 +16,16 @@
|
||||||
"title": "Nenalezeno",
|
"title": "Nenalezeno",
|
||||||
"text": "Požadovaná stránka neexistuje."
|
"text": "Požadovaná stránka neexistuje."
|
||||||
},
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Vikunja se načítá…",
|
||||||
|
"errorOccured": "Došlo k chybě:",
|
||||||
|
"checkApiUrl": "Zkontrolujte, zda je adresa URL API správná.",
|
||||||
|
"noApiUrlConfigured": "Nebyla nakonfigurována žádná adresa API. Prosím nastavte jednu níže:"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "Jste offline.",
|
||||||
|
"text": "Zkontrolujte své internetové připojení a zkuste to znovu."
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"username": "Uživatelské jméno",
|
"username": "Uživatelské jméno",
|
||||||
|
@ -102,6 +112,15 @@
|
||||||
"disabled": "Vypnuto",
|
"disabled": "Vypnuto",
|
||||||
"todoist": "Todoist",
|
"todoist": "Todoist",
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
|
@ -344,6 +363,7 @@
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"title": "Filtry",
|
"title": "Filtry",
|
||||||
|
"clear": "Vymazat filtry",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"title": "Název",
|
"title": "Název",
|
||||||
"titlePlaceholder": "Název uloženého filtru přijde sem…",
|
"titlePlaceholder": "Název uloženého filtru přijde sem…",
|
||||||
|
@ -449,7 +469,9 @@
|
||||||
"saved": "Uloženo!",
|
"saved": "Uloženo!",
|
||||||
"default": "Výchozí",
|
"default": "Výchozí",
|
||||||
"close": "Zavřít",
|
"close": "Zavřít",
|
||||||
"download": "Stáhnout"
|
"download": "Stáhnout",
|
||||||
|
"showMenu": "Zobrazit nabídku",
|
||||||
|
"hideMenu": "Skrýt nabídku"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"resetColor": "Obnovit barvu",
|
"resetColor": "Obnovit barvu",
|
||||||
|
@ -534,7 +556,7 @@
|
||||||
"text2": "Tímto také odstraníte všechny přílohy, připomenutí a vztahy spojené s tímto úkolem a nelze je vrátit zpět!"
|
"text2": "Tímto také odstraníte všechny přílohy, připomenutí a vztahy spojené s tímto úkolem a nelze je vrátit zpět!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Přiřadit tuto úlohu uživateli",
|
"assign": "Assign to a user",
|
||||||
"label": "Přidat štítky",
|
"label": "Přidat štítky",
|
||||||
"priority": "Nastavit prioritu",
|
"priority": "Nastavit prioritu",
|
||||||
"dueDate": "Nastavit termín",
|
"dueDate": "Nastavit termín",
|
||||||
|
@ -643,6 +665,7 @@
|
||||||
"searchPlaceholder": "Hledejte nový úkol, který chcete přidat jako související…",
|
"searchPlaceholder": "Hledejte nový úkol, který chcete přidat jako související…",
|
||||||
"createPlaceholder": "Přidat toto jako nový související úkol",
|
"createPlaceholder": "Přidat toto jako nový související úkol",
|
||||||
"differentList": "Tento úkol patří do jiného seznamu.",
|
"differentList": "Tento úkol patří do jiného seznamu.",
|
||||||
|
"differentNamespace": "Tento úkol patří do jiného prostoru.",
|
||||||
"noneYet": "Zatím žádné vztahy mezi úkoly.",
|
"noneYet": "Zatím žádné vztahy mezi úkoly.",
|
||||||
"delete": "Odstranit vztah k úloze",
|
"delete": "Odstranit vztah k úloze",
|
||||||
"deleteText1": "Jste si jisti, že chcete odstranit tento vztah úkolu?",
|
"deleteText1": "Jste si jisti, že chcete odstranit tento vztah úkolu?",
|
||||||
|
@ -743,18 +766,27 @@
|
||||||
},
|
},
|
||||||
"keyboardShortcuts": {
|
"keyboardShortcuts": {
|
||||||
"title": "Klávesové zkratky",
|
"title": "Klávesové zkratky",
|
||||||
|
"general": "Obecné",
|
||||||
"allPages": "Tyto zkratky fungují na všech stránkách.",
|
"allPages": "Tyto zkratky fungují na všech stránkách.",
|
||||||
"currentPageOnly": "Tyto zkratky fungují pouze na aktuální stránce.",
|
"currentPageOnly": "Tyto zkratky fungují pouze na aktuální stránce.",
|
||||||
"toggleMenu": "Přepnout nabídku",
|
"toggleMenu": "Přepnout nabídku",
|
||||||
"quickSearch": "Otevřít vyhledávání / panel rychlých akcí",
|
"quickSearch": "Otevřít vyhledávání / panel rychlých akcí",
|
||||||
|
"then": "potom",
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Stránka úkolů",
|
"title": "Stránka úkolů",
|
||||||
"done": "Označit úkol jako hotový",
|
"done": "Označit úkol jako hotový",
|
||||||
"assign": "Přiřadit tento úkol uživateli",
|
"assign": "Assign to a user",
|
||||||
"labels": "Přidat štítky k tomuto úkolu",
|
"labels": "Přidat štítky k tomuto úkolu",
|
||||||
"dueDate": "Změnit termín tohoto úkolu",
|
"dueDate": "Změnit termín tohoto úkolu",
|
||||||
"attachment": "Přidat přílohu k tomuto úkolu",
|
"attachment": "Přidat přílohu k tomuto úkolu",
|
||||||
"related": "Upravit související úkoly tohoto úkolu"
|
"related": "Upravit související úkoly tohoto úkolu"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "Zobrazení seznamů",
|
||||||
|
"switchToListView": "Přepnout na zobrazení seznamu",
|
||||||
|
"switchToGanttView": "Přepnout na zobrazení gantt",
|
||||||
|
"switchToKanbanView": "Přepnout na zobrazení kanbanu",
|
||||||
|
"switchToTableView": "Přepnout na zobrazení tabulky"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
|
@ -776,8 +808,9 @@
|
||||||
"urlPlaceholder": "např. https://localhost:3456",
|
"urlPlaceholder": "např. https://localhost:3456",
|
||||||
"change": "změnit",
|
"change": "změnit",
|
||||||
"signInOn": "Přihlaste se ke svému účtu Vikunja na {0}",
|
"signInOn": "Přihlaste se ke svému účtu Vikunja na {0}",
|
||||||
"error": "Nelze najít nebo použít instalaci Vikunja na \"{domain}\".",
|
"error": "Nelze najít nebo použít instalaci Vikunja na \"{domain}\". Zkuste prosím jinou url.",
|
||||||
"success": "Pomocí instalace Vikunja na \"{domain}\"."
|
"success": "Pomocí instalace Vikunja na \"{domain}\".",
|
||||||
|
"urlRequired": "Je vyžadována adresa URL."
|
||||||
},
|
},
|
||||||
"loadingError": {
|
"loadingError": {
|
||||||
"failed": "Načítání selhalo, prosím {0}. Pokud chyba přetrvává, {1}.",
|
"failed": "Načítání selhalo, prosím {0}. Pokud chyba přetrvává, {1}.",
|
||||||
|
@ -792,7 +825,7 @@
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"commands": "Příkazy",
|
"commands": "Příkazy",
|
||||||
"placeholder": "Napište příkaz nebo vyhledávání…",
|
"placeholder": "Napište příkaz nebo vyhledávání…",
|
||||||
"hint": "Můžete použít # pro hledání úkolů, * pro hledání seznamů a @ pro hledání týmů.",
|
"hint": "Můžete použít {list} k omezení hledání na seznam. Kombinujte {list} nebo {label} (štítky) s vyhledávacím dotazem pro hledání úkolu s těmito štítky nebo na tomto seznamu. Použijte {assignee} pouze pro hledání týmů.",
|
||||||
"tasks": "Úkoly",
|
"tasks": "Úkoly",
|
||||||
"lists": "Seznamy",
|
"lists": "Seznamy",
|
||||||
"teams": "Týmy",
|
"teams": "Týmy",
|
||||||
|
|
|
@ -16,6 +16,16 @@
|
||||||
"title": "Nicht gefunden",
|
"title": "Nicht gefunden",
|
||||||
"text": "Die angeforderte Seite existiert nicht."
|
"text": "Die angeforderte Seite existiert nicht."
|
||||||
},
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Vikunja wird geladen…",
|
||||||
|
"errorOccured": "Es ist ein Fehler aufgetreten:",
|
||||||
|
"checkApiUrl": "Bitte überprüfe, ob die API-Url stimmt.",
|
||||||
|
"noApiUrlConfigured": "Es wurde keine API-Url konfiguriert. Bitte stelle unten eine ein:"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "Du bist offline.",
|
||||||
|
"text": "Bitte überprüfe die Netzwerkverbindung und versuche es erneut."
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"username": "Anmeldename",
|
"username": "Anmeldename",
|
||||||
|
@ -102,6 +112,15 @@
|
||||||
"disabled": "Deaktiviert",
|
"disabled": "Deaktiviert",
|
||||||
"todoist": "Todoist",
|
"todoist": "Todoist",
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
|
@ -344,6 +363,7 @@
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"title": "Filter",
|
"title": "Filter",
|
||||||
|
"clear": "Filter zurücksetzen",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"title": "Titel",
|
"title": "Titel",
|
||||||
"titlePlaceholder": "Einen gespeicherten Filternamen eingeben …",
|
"titlePlaceholder": "Einen gespeicherten Filternamen eingeben …",
|
||||||
|
@ -449,7 +469,9 @@
|
||||||
"saved": "Gespeichert!",
|
"saved": "Gespeichert!",
|
||||||
"default": "Standard",
|
"default": "Standard",
|
||||||
"close": "Schließen",
|
"close": "Schließen",
|
||||||
"download": "Herunterladen"
|
"download": "Herunterladen",
|
||||||
|
"showMenu": "Menü anzeigen",
|
||||||
|
"hideMenu": "Menü ausblenden"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"resetColor": "Farbe zurücksetzen",
|
"resetColor": "Farbe zurücksetzen",
|
||||||
|
@ -534,7 +556,7 @@
|
||||||
"text2": "Dies wird auch alle Anhänge, Erinnerungen und Verknüpfungen, die zu dieser Aufgabe gehören löschen und kann nicht rückgängig gemacht werden!"
|
"text2": "Dies wird auch alle Anhänge, Erinnerungen und Verknüpfungen, die zu dieser Aufgabe gehören löschen und kann nicht rückgängig gemacht werden!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Diese Aufgabe jemandem zuweisen",
|
"assign": "Assign to a user",
|
||||||
"label": "Label hinzufügen",
|
"label": "Label hinzufügen",
|
||||||
"priority": "Priorität setzen",
|
"priority": "Priorität setzen",
|
||||||
"dueDate": "Fälligkeitsdatum setzen",
|
"dueDate": "Fälligkeitsdatum setzen",
|
||||||
|
@ -643,6 +665,7 @@
|
||||||
"searchPlaceholder": "Beginne zu schreiben, um eine Aufgabe zu suchen, die als Beziehung hinzugefügt werden soll…",
|
"searchPlaceholder": "Beginne zu schreiben, um eine Aufgabe zu suchen, die als Beziehung hinzugefügt werden soll…",
|
||||||
"createPlaceholder": "Füge diese Aufgabe als neue Aufgabenbeziehung hinzu",
|
"createPlaceholder": "Füge diese Aufgabe als neue Aufgabenbeziehung hinzu",
|
||||||
"differentList": "Diese Aufgabe gehört zu einer anderen Liste.",
|
"differentList": "Diese Aufgabe gehört zu einer anderen Liste.",
|
||||||
|
"differentNamespace": "Diese Aufgabe gehört zu einem anderen Namespace.",
|
||||||
"noneYet": "Keine Aufgabenbeziehung vorhanden.",
|
"noneYet": "Keine Aufgabenbeziehung vorhanden.",
|
||||||
"delete": "Aufgabenbeziehung entfernen",
|
"delete": "Aufgabenbeziehung entfernen",
|
||||||
"deleteText1": "Willst du diese Aufgabenbeziehung wirklich entfernen?",
|
"deleteText1": "Willst du diese Aufgabenbeziehung wirklich entfernen?",
|
||||||
|
@ -743,18 +766,27 @@
|
||||||
},
|
},
|
||||||
"keyboardShortcuts": {
|
"keyboardShortcuts": {
|
||||||
"title": "Tastenkürzel",
|
"title": "Tastenkürzel",
|
||||||
|
"general": "Allgemein",
|
||||||
"allPages": "Diese Tastenkürzel funktionieren auf allen Seiten.",
|
"allPages": "Diese Tastenkürzel funktionieren auf allen Seiten.",
|
||||||
"currentPageOnly": "Diese Tastenkürzel funktionieren nur auf der aktuellen Seite.",
|
"currentPageOnly": "Diese Tastenkürzel funktionieren nur auf der aktuellen Seite.",
|
||||||
"toggleMenu": "Das Menü umschalten",
|
"toggleMenu": "Das Menü umschalten",
|
||||||
"quickSearch": "Such-/Schnellaktionsleiste öffnen",
|
"quickSearch": "Such-/Schnellaktionsleiste öffnen",
|
||||||
|
"then": "dann",
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Aufgabenseite",
|
"title": "Aufgabenseite",
|
||||||
"done": "Eine Aufgabe als erledigt markieren",
|
"done": "Eine Aufgabe als erledigt markieren",
|
||||||
"assign": "Diese Aufgabe jemandem zuweisen",
|
"assign": "Assign to a user",
|
||||||
"labels": "Dieser Aufgabe ein Label hinzufügen",
|
"labels": "Dieser Aufgabe ein Label hinzufügen",
|
||||||
"dueDate": "Ändere das Fälligkeitsdatum dieser Aufgabe",
|
"dueDate": "Ändere das Fälligkeitsdatum dieser Aufgabe",
|
||||||
"attachment": "Einen Anhang dieser Aufgabe hinzufügen",
|
"attachment": "Einen Anhang dieser Aufgabe hinzufügen",
|
||||||
"related": "Ändere die Abhängigen Aufgaben dieser Aufgabe"
|
"related": "Ändere die Abhängigen Aufgaben dieser Aufgabe"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "Listenansicht",
|
||||||
|
"switchToListView": "Zur Listenansicht wechseln",
|
||||||
|
"switchToGanttView": "Zur Ganttansicht wechseln",
|
||||||
|
"switchToKanbanView": "Zur Kanbanansicht wechseln",
|
||||||
|
"switchToTableView": "Zur Tabellenansicht wechseln"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
|
@ -776,8 +808,9 @@
|
||||||
"urlPlaceholder": "z.B. https://localhost:3456",
|
"urlPlaceholder": "z.B. https://localhost:3456",
|
||||||
"change": "ändern",
|
"change": "ändern",
|
||||||
"signInOn": "Melde dich bei deinem Vikunja-Account auf {0} an",
|
"signInOn": "Melde dich bei deinem Vikunja-Account auf {0} an",
|
||||||
"error": "Konnte keine Vikunja-Installation unter „{domain}“ finden oder verwenden.",
|
"error": "Konnte keine Vikunja-Installation unter „{domain}“ finden oder verwenden. Bitte probiere eine andere Url.",
|
||||||
"success": "Verwende die Vikunja-Installation unter „{domain}“."
|
"success": "Verwende die Vikunja-Installation unter „{domain}“.",
|
||||||
|
"urlRequired": "Eine Url ist erforderlich."
|
||||||
},
|
},
|
||||||
"loadingError": {
|
"loadingError": {
|
||||||
"failed": "Laden fehlgeschlagen, bitte {0}. Wenn der Fehler weiterhin besteht, {1} bitte.",
|
"failed": "Laden fehlgeschlagen, bitte {0}. Wenn der Fehler weiterhin besteht, {1} bitte.",
|
||||||
|
@ -792,7 +825,7 @@
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"commands": "Befehle",
|
"commands": "Befehle",
|
||||||
"placeholder": "Gib einen Befehl oder eine Suche ein …",
|
"placeholder": "Gib einen Befehl oder eine Suche ein …",
|
||||||
"hint": "Du kannst # verwenden, um nur nach Aufgaben zu suchen, *, um nur nach Listen zu suchen und @, um nur nach Teams zu suchen.",
|
"hint": "Du kannst {list} verwenden, um die Suche auf eine Liste zu beschränken. Kombiniere {list} oder {label} (Labels) mit einer Suchabfrage, um eine Aufgabe mit diesen Labels oder auf dieser Liste zu suchen. Verwende {assignee}, um nur nach Teams zu suchen.",
|
||||||
"tasks": "Aufgaben",
|
"tasks": "Aufgaben",
|
||||||
"lists": "Listen",
|
"lists": "Listen",
|
||||||
"teams": "Teams",
|
"teams": "Teams",
|
||||||
|
|
|
@ -16,6 +16,16 @@
|
||||||
"title": "Nid gfunde",
|
"title": "Nid gfunde",
|
||||||
"text": "Dini gsuechti Siite giz nid."
|
"text": "Dini gsuechti Siite giz nid."
|
||||||
},
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Vikunja wird geladen…",
|
||||||
|
"errorOccured": "Es ist ein Fehler aufgetreten:",
|
||||||
|
"checkApiUrl": "Bitte überprüfe, ob die API-Url stimmt.",
|
||||||
|
"noApiUrlConfigured": "Es wurde keine API-Url konfiguriert. Bitte stelle unten eine ein:"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "Du bist offline.",
|
||||||
|
"text": "Bitte überprüfe die Netzwerkverbindung und versuche es erneut."
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"username": "Benutzernamä",
|
"username": "Benutzernamä",
|
||||||
|
@ -102,6 +112,15 @@
|
||||||
"disabled": "Deaktiviert",
|
"disabled": "Deaktiviert",
|
||||||
"todoist": "Todoist",
|
"todoist": "Todoist",
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
|
@ -344,6 +363,7 @@
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"title": "Filter",
|
"title": "Filter",
|
||||||
|
"clear": "Filter zurücksetzen",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"title": "Titl",
|
"title": "Titl",
|
||||||
"titlePlaceholder": "De Name für de g'speicheret Filter chunt da ahne…",
|
"titlePlaceholder": "De Name für de g'speicheret Filter chunt da ahne…",
|
||||||
|
@ -449,7 +469,9 @@
|
||||||
"saved": "Gspeicheret!",
|
"saved": "Gspeicheret!",
|
||||||
"default": "Standard",
|
"default": "Standard",
|
||||||
"close": "Schlüüse",
|
"close": "Schlüüse",
|
||||||
"download": "Herunterladen"
|
"download": "Herunterladen",
|
||||||
|
"showMenu": "Menü anzeigen",
|
||||||
|
"hideMenu": "Menü ausblenden"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"resetColor": "Farb zruggsetze",
|
"resetColor": "Farb zruggsetze",
|
||||||
|
@ -534,7 +556,7 @@
|
||||||
"text2": "Das wird au alli Ahhäng, Errinnerige und Beziehige wo mit dere Uufgab verchnüpft sind chüble und cha nid rückgängig gmacht werde!"
|
"text2": "Das wird au alli Ahhäng, Errinnerige und Beziehige wo mit dere Uufgab verchnüpft sind chüble und cha nid rückgängig gmacht werde!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Die Uufgab emne Benutzer zuewiise",
|
"assign": "Assign to a user",
|
||||||
"label": "Label hinzuefüege",
|
"label": "Label hinzuefüege",
|
||||||
"priority": "Priorität setzä",
|
"priority": "Priorität setzä",
|
||||||
"dueDate": "Fälligkeitsdatum setze",
|
"dueDate": "Fälligkeitsdatum setze",
|
||||||
|
@ -643,6 +665,7 @@
|
||||||
"searchPlaceholder": "Schriib, um e neui Uufgab als Zueghörigkeit hinzuezfüege…",
|
"searchPlaceholder": "Schriib, um e neui Uufgab als Zueghörigkeit hinzuezfüege…",
|
||||||
"createPlaceholder": "Das als en neui Zueghörigkeit hinzuefüege",
|
"createPlaceholder": "Das als en neui Zueghörigkeit hinzuefüege",
|
||||||
"differentList": "Die Uufgab ghöört zu ere andere Liste.",
|
"differentList": "Die Uufgab ghöört zu ere andere Liste.",
|
||||||
|
"differentNamespace": "Diese Aufgabe gehört zu einem anderen Namespace.",
|
||||||
"noneYet": "S'git kei Uufgabe Beziehige.",
|
"noneYet": "S'git kei Uufgabe Beziehige.",
|
||||||
"delete": "Uufgabe Beziehig chüble",
|
"delete": "Uufgabe Beziehig chüble",
|
||||||
"deleteText1": "Bisch du dir sicher, dass du die Zueghörigkeit chüblä wetsch?",
|
"deleteText1": "Bisch du dir sicher, dass du die Zueghörigkeit chüblä wetsch?",
|
||||||
|
@ -743,18 +766,27 @@
|
||||||
},
|
},
|
||||||
"keyboardShortcuts": {
|
"keyboardShortcuts": {
|
||||||
"title": "Tastatuurchürzl",
|
"title": "Tastatuurchürzl",
|
||||||
|
"general": "Allgemein",
|
||||||
"allPages": "Die Chürzl funktioniered uf allne Siitene.",
|
"allPages": "Die Chürzl funktioniered uf allne Siitene.",
|
||||||
"currentPageOnly": "Die Chürzl funktioniered nur uf de momentane Siite.",
|
"currentPageOnly": "Die Chürzl funktioniered nur uf de momentane Siite.",
|
||||||
"toggleMenu": "Menü umschalte",
|
"toggleMenu": "Menü umschalte",
|
||||||
"quickSearch": "Suechi und Schnellaktionsliste öffne",
|
"quickSearch": "Suechi und Schnellaktionsliste öffne",
|
||||||
|
"then": "dann",
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Uufgabesiite",
|
"title": "Uufgabesiite",
|
||||||
"done": "Uufgab als erledigt markiere",
|
"done": "Uufgab als erledigt markiere",
|
||||||
"assign": "Die Uufgab emne Benutzer zuewiise",
|
"assign": "Assign to a user",
|
||||||
"labels": "Labels ennere Uufgab hinzuefüege",
|
"labels": "Labels ennere Uufgab hinzuefüege",
|
||||||
"dueDate": "S'Fälligkeitsdatum für die Uufgab ändere",
|
"dueDate": "S'Fälligkeitsdatum für die Uufgab ändere",
|
||||||
"attachment": "En Aahang dere Uufgab hinzuefüege",
|
"attachment": "En Aahang dere Uufgab hinzuefüege",
|
||||||
"related": "Beziehige vo dere Uufgab bearbeite"
|
"related": "Beziehige vo dere Uufgab bearbeite"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "Listenansicht",
|
||||||
|
"switchToListView": "Zur Listenansicht wechseln",
|
||||||
|
"switchToGanttView": "Zur Ganttansicht wechseln",
|
||||||
|
"switchToKanbanView": "Zur Kanbanansicht wechseln",
|
||||||
|
"switchToTableView": "Zur Tabellenansicht wechseln"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
|
@ -776,8 +808,9 @@
|
||||||
"urlPlaceholder": "z.B. https://localhost:3456",
|
"urlPlaceholder": "z.B. https://localhost:3456",
|
||||||
"change": "ändere",
|
"change": "ändere",
|
||||||
"signInOn": "Dich i diin Vikunja-Account Iihloge uf {0}",
|
"signInOn": "Dich i diin Vikunja-Account Iihloge uf {0}",
|
||||||
"error": "Es het kei Vikunja Installation uf \"{domain}\".",
|
"error": "Konnte keine Vikunja-Installation unter „{domain}“ finden oder verwenden. Bitte probiere eine andere Url.",
|
||||||
"success": "Benutze d'Vikunja Installation uf \"{domain}\"."
|
"success": "Benutze d'Vikunja Installation uf \"{domain}\".",
|
||||||
|
"urlRequired": "Eine Url ist erforderlich."
|
||||||
},
|
},
|
||||||
"loadingError": {
|
"loadingError": {
|
||||||
"failed": "Ladä isch fählgschlage, bitte {0}. Wenn de Fähler bestaht, denn {1}.",
|
"failed": "Ladä isch fählgschlage, bitte {0}. Wenn de Fähler bestaht, denn {1}.",
|
||||||
|
@ -792,7 +825,7 @@
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"commands": "Befehl",
|
"commands": "Befehl",
|
||||||
"placeholder": "Schriib en Befehl oder suech…",
|
"placeholder": "Schriib en Befehl oder suech…",
|
||||||
"hint": "Du chasch en # benutze, um nur nach Uufgabe zsueche, * um nur nach Liste zsueche und @ um nur Teams z'sueche.",
|
"hint": "Du kannst {list} verwenden, um die Suche auf eine Liste zu beschränken. Kombiniere {list} oder {label} (Labels) mit einer Suchabfrage, um eine Aufgabe mit diesen Labels oder auf dieser Liste zu suchen. Verwende {assignee}, um nur nach Teams zu suchen.",
|
||||||
"tasks": "Uufgabe",
|
"tasks": "Uufgabe",
|
||||||
"lists": "Listene",
|
"lists": "Listene",
|
||||||
"teams": "Teams",
|
"teams": "Teams",
|
||||||
|
|
|
@ -16,6 +16,16 @@
|
||||||
"title": "Not found",
|
"title": "Not found",
|
||||||
"text": "The page you requested does not exist."
|
"text": "The page you requested does not exist."
|
||||||
},
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Vikunja is loading…",
|
||||||
|
"errorOccured": "An error occured:",
|
||||||
|
"checkApiUrl": "Please check if the api url is correct.",
|
||||||
|
"noApiUrlConfigured": "No API url was configured. Please set one below:"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "You are offline.",
|
||||||
|
"text": "Please check your network connection and try again."
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
|
@ -102,6 +112,15 @@
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"todoist": "Todoist",
|
"todoist": "Todoist",
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
|
@ -344,6 +363,7 @@
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"title": "Filters",
|
"title": "Filters",
|
||||||
|
"clear": "Clear Filters",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"titlePlaceholder": "The saved filter title goes here…",
|
"titlePlaceholder": "The saved filter title goes here…",
|
||||||
|
@ -449,7 +469,9 @@
|
||||||
"saved": "Saved!",
|
"saved": "Saved!",
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"download": "Download"
|
"download": "Download",
|
||||||
|
"showMenu": "Show the menu",
|
||||||
|
"hideMenu": "Hide the menu"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"resetColor": "Reset Color",
|
"resetColor": "Reset Color",
|
||||||
|
@ -534,7 +556,7 @@
|
||||||
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
|
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Assign this task to a user",
|
"assign": "Assign to a user",
|
||||||
"label": "Add labels",
|
"label": "Add labels",
|
||||||
"priority": "Set Priority",
|
"priority": "Set Priority",
|
||||||
"dueDate": "Set Due Date",
|
"dueDate": "Set Due Date",
|
||||||
|
@ -643,6 +665,7 @@
|
||||||
"searchPlaceholder": "Type search for a new task to add as related…",
|
"searchPlaceholder": "Type search for a new task to add as related…",
|
||||||
"createPlaceholder": "Add this as new related task",
|
"createPlaceholder": "Add this as new related task",
|
||||||
"differentList": "This task belongs to a different list.",
|
"differentList": "This task belongs to a different list.",
|
||||||
|
"differentNamespace": "This task belongs to a different namespace.",
|
||||||
"noneYet": "No task relations yet.",
|
"noneYet": "No task relations yet.",
|
||||||
"delete": "Delete Task Relation",
|
"delete": "Delete Task Relation",
|
||||||
"deleteText1": "Are you sure you want to delete this task relation?",
|
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||||
|
@ -743,18 +766,27 @@
|
||||||
},
|
},
|
||||||
"keyboardShortcuts": {
|
"keyboardShortcuts": {
|
||||||
"title": "Keyboard Shortcuts",
|
"title": "Keyboard Shortcuts",
|
||||||
|
"general": "General",
|
||||||
"allPages": "These shortcuts work on all pages.",
|
"allPages": "These shortcuts work on all pages.",
|
||||||
"currentPageOnly": "These shortcuts work only on the current page.",
|
"currentPageOnly": "These shortcuts work only on the current page.",
|
||||||
"toggleMenu": "Toggle The Menu",
|
"toggleMenu": "Toggle The Menu",
|
||||||
"quickSearch": "Open the search/quick action bar",
|
"quickSearch": "Open the search/quick action bar",
|
||||||
|
"then": "then",
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Task Page",
|
"title": "Task Page",
|
||||||
"done": "Mark a task as done",
|
"done": "Mark a task as done",
|
||||||
"assign": "Assign this task to a user",
|
"assign": "Assign to a user",
|
||||||
"labels": "Add labels to this task",
|
"labels": "Add labels to this task",
|
||||||
"dueDate": "Change the due date of this task",
|
"dueDate": "Change the due date of this task",
|
||||||
"attachment": "Add an attachment to this task",
|
"attachment": "Add an attachment to this task",
|
||||||
"related": "Modify related tasks of this task"
|
"related": "Modify related tasks of this task"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "List Views",
|
||||||
|
"switchToListView": "Switch to list view",
|
||||||
|
"switchToGanttView": "Switch to gantt view",
|
||||||
|
"switchToKanbanView": "Switch to kanban view",
|
||||||
|
"switchToTableView": "Switch to table view"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
|
@ -776,8 +808,9 @@
|
||||||
"urlPlaceholder": "eg. https://localhost:3456",
|
"urlPlaceholder": "eg. https://localhost:3456",
|
||||||
"change": "change",
|
"change": "change",
|
||||||
"signInOn": "Sign in to your Vikunja account on {0}",
|
"signInOn": "Sign in to your Vikunja account on {0}",
|
||||||
"error": "Could not find or use Vikunja installation at \"{domain}\".",
|
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.",
|
||||||
"success": "Using Vikunja installation at \"{domain}\"."
|
"success": "Using Vikunja installation at \"{domain}\".",
|
||||||
|
"urlRequired": "A url is required."
|
||||||
},
|
},
|
||||||
"loadingError": {
|
"loadingError": {
|
||||||
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||||
|
@ -792,7 +825,7 @@
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"commands": "Commands",
|
"commands": "Commands",
|
||||||
"placeholder": "Type a command or search…",
|
"placeholder": "Type a command or search…",
|
||||||
"hint": "You can use # to only seach for tasks, * to only search for lists and @ to only search for teams.",
|
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
|
||||||
"tasks": "Tasks",
|
"tasks": "Tasks",
|
||||||
"lists": "Lists",
|
"lists": "Lists",
|
||||||
"teams": "Teams",
|
"teams": "Teams",
|
||||||
|
|
|
@ -16,6 +16,16 @@
|
||||||
"title": "Not found",
|
"title": "Not found",
|
||||||
"text": "The page you requested does not exist."
|
"text": "The page you requested does not exist."
|
||||||
},
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Vikunja is loading…",
|
||||||
|
"errorOccured": "An error occured:",
|
||||||
|
"checkApiUrl": "Please check if the api url is correct.",
|
||||||
|
"noApiUrlConfigured": "No API url was configured. Please set one below:"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "You are offline.",
|
||||||
|
"text": "Please check your network connection and try again."
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
|
@ -102,6 +112,15 @@
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"todoist": "Todoist",
|
"todoist": "Todoist",
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
|
@ -344,6 +363,7 @@
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"title": "Filters",
|
"title": "Filters",
|
||||||
|
"clear": "Clear Filters",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"titlePlaceholder": "The saved filter title goes here…",
|
"titlePlaceholder": "The saved filter title goes here…",
|
||||||
|
@ -449,7 +469,9 @@
|
||||||
"saved": "Saved!",
|
"saved": "Saved!",
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"download": "Download"
|
"download": "Download",
|
||||||
|
"showMenu": "Show the menu",
|
||||||
|
"hideMenu": "Hide the menu"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"resetColor": "Reset Color",
|
"resetColor": "Reset Color",
|
||||||
|
@ -534,7 +556,7 @@
|
||||||
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
|
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Assign this task to a user",
|
"assign": "Assign to a user",
|
||||||
"label": "Add labels",
|
"label": "Add labels",
|
||||||
"priority": "Set Priority",
|
"priority": "Set Priority",
|
||||||
"dueDate": "Set Due Date",
|
"dueDate": "Set Due Date",
|
||||||
|
@ -643,6 +665,7 @@
|
||||||
"searchPlaceholder": "Type search for a new task to add as related…",
|
"searchPlaceholder": "Type search for a new task to add as related…",
|
||||||
"createPlaceholder": "Add this as new related task",
|
"createPlaceholder": "Add this as new related task",
|
||||||
"differentList": "This task belongs to a different list.",
|
"differentList": "This task belongs to a different list.",
|
||||||
|
"differentNamespace": "This task belongs to a different namespace.",
|
||||||
"noneYet": "No task relations yet.",
|
"noneYet": "No task relations yet.",
|
||||||
"delete": "Delete Task Relation",
|
"delete": "Delete Task Relation",
|
||||||
"deleteText1": "Are you sure you want to delete this task relation?",
|
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||||
|
@ -743,18 +766,27 @@
|
||||||
},
|
},
|
||||||
"keyboardShortcuts": {
|
"keyboardShortcuts": {
|
||||||
"title": "Keyboard Shortcuts",
|
"title": "Keyboard Shortcuts",
|
||||||
|
"general": "General",
|
||||||
"allPages": "These shortcuts work on all pages.",
|
"allPages": "These shortcuts work on all pages.",
|
||||||
"currentPageOnly": "These shortcuts work only on the current page.",
|
"currentPageOnly": "These shortcuts work only on the current page.",
|
||||||
"toggleMenu": "Toggle The Menu",
|
"toggleMenu": "Toggle The Menu",
|
||||||
"quickSearch": "Open the search/quick action bar",
|
"quickSearch": "Open the search/quick action bar",
|
||||||
|
"then": "then",
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Task Page",
|
"title": "Task Page",
|
||||||
"done": "Mark a task as done",
|
"done": "Mark a task as done",
|
||||||
"assign": "Assign this task to a user",
|
"assign": "Assign to a user",
|
||||||
"labels": "Add labels to this task",
|
"labels": "Add labels to this task",
|
||||||
"dueDate": "Change the due date of this task",
|
"dueDate": "Change the due date of this task",
|
||||||
"attachment": "Add an attachment to this task",
|
"attachment": "Add an attachment to this task",
|
||||||
"related": "Modify related tasks of this task"
|
"related": "Modify related tasks of this task"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "List Views",
|
||||||
|
"switchToListView": "Switch to list view",
|
||||||
|
"switchToGanttView": "Switch to gantt view",
|
||||||
|
"switchToKanbanView": "Switch to kanban view",
|
||||||
|
"switchToTableView": "Switch to table view"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
|
@ -776,8 +808,9 @@
|
||||||
"urlPlaceholder": "eg. https://localhost:3456",
|
"urlPlaceholder": "eg. https://localhost:3456",
|
||||||
"change": "change",
|
"change": "change",
|
||||||
"signInOn": "Sign in to your Vikunja account on {0}",
|
"signInOn": "Sign in to your Vikunja account on {0}",
|
||||||
"error": "Could not find or use Vikunja installation at \"{domain}\".",
|
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.",
|
||||||
"success": "Using Vikunja installation at \"{domain}\"."
|
"success": "Using Vikunja installation at \"{domain}\".",
|
||||||
|
"urlRequired": "A url is required."
|
||||||
},
|
},
|
||||||
"loadingError": {
|
"loadingError": {
|
||||||
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||||
|
@ -792,7 +825,7 @@
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"commands": "Commands",
|
"commands": "Commands",
|
||||||
"placeholder": "Type a command or search…",
|
"placeholder": "Type a command or search…",
|
||||||
"hint": "You can use # to only seach for tasks, * to only search for lists and @ to only search for teams.",
|
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
|
||||||
"tasks": "Tasks",
|
"tasks": "Tasks",
|
||||||
"lists": "Lists",
|
"lists": "Lists",
|
||||||
"teams": "Teams",
|
"teams": "Teams",
|
||||||
|
|
|
@ -16,6 +16,16 @@
|
||||||
"title": "Non trouvé",
|
"title": "Non trouvé",
|
||||||
"text": "La page que tu as demandée n’existe pas."
|
"text": "La page que tu as demandée n’existe pas."
|
||||||
},
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Chargement de Vikunja en cours…",
|
||||||
|
"errorOccured": "Une erreur s'est produite :",
|
||||||
|
"checkApiUrl": "Veuillez vérifier que l'URL de l'API est valide.",
|
||||||
|
"noApiUrlConfigured": "Aucune URL API n'a été configurée. Veuillez en définir une ci-dessous :"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "Vous êtes hors ligne.",
|
||||||
|
"text": "Veuillez vérifier votre connexion réseau et réessayer."
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"username": "Nom d’utilisateur·rice",
|
"username": "Nom d’utilisateur·rice",
|
||||||
|
@ -102,6 +112,15 @@
|
||||||
"disabled": "Désactivé",
|
"disabled": "Désactivé",
|
||||||
"todoist": "Todoist",
|
"todoist": "Todoist",
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
|
@ -344,6 +363,7 @@
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"title": "Filtres",
|
"title": "Filtres",
|
||||||
|
"clear": "Effacer les filtres",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"title": "Nom",
|
"title": "Nom",
|
||||||
"titlePlaceholder": "Entre un nom de filtre enregistré…",
|
"titlePlaceholder": "Entre un nom de filtre enregistré…",
|
||||||
|
@ -449,7 +469,9 @@
|
||||||
"saved": "Enregistré !",
|
"saved": "Enregistré !",
|
||||||
"default": "Par défaut",
|
"default": "Par défaut",
|
||||||
"close": "Fermer",
|
"close": "Fermer",
|
||||||
"download": "Télécharger"
|
"download": "Télécharger",
|
||||||
|
"showMenu": "Afficher le menu",
|
||||||
|
"hideMenu": "Masquer le menu"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"resetColor": "Réinitialiser la couleur",
|
"resetColor": "Réinitialiser la couleur",
|
||||||
|
@ -511,7 +533,7 @@
|
||||||
"today": "Aujourd’hui",
|
"today": "Aujourd’hui",
|
||||||
"nextWeek": "La semaine prochaine",
|
"nextWeek": "La semaine prochaine",
|
||||||
"nextMonth": "Le mois prochain",
|
"nextMonth": "Le mois prochain",
|
||||||
"noTasks": "Nothing to do — Have a nice day!"
|
"noTasks": "Rien à faire — Passe une bonne journée !"
|
||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
"chooseDueDate": "Clique ici pour définir une date d’échéance",
|
"chooseDueDate": "Clique ici pour définir une date d’échéance",
|
||||||
|
@ -534,7 +556,7 @@
|
||||||
"text2": "Ceci supprimera également toutes les pièces jointes, les rappels et les relations associés à cette tâche et ne pourra pas être annulé !"
|
"text2": "Ceci supprimera également toutes les pièces jointes, les rappels et les relations associés à cette tâche et ne pourra pas être annulé !"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Assigner cette tâche à un·e utilisateur·rice",
|
"assign": "Assign to a user",
|
||||||
"label": "Ajouter des étiquettes",
|
"label": "Ajouter des étiquettes",
|
||||||
"priority": "Définir la priorité",
|
"priority": "Définir la priorité",
|
||||||
"dueDate": "Définir l’échéance",
|
"dueDate": "Définir l’échéance",
|
||||||
|
@ -643,6 +665,7 @@
|
||||||
"searchPlaceholder": "Écris la recherche d’une nouvelle tâche à ajouter comme connexe…",
|
"searchPlaceholder": "Écris la recherche d’une nouvelle tâche à ajouter comme connexe…",
|
||||||
"createPlaceholder": "Ajouter cette tâche comme nouvelle tâche connexe",
|
"createPlaceholder": "Ajouter cette tâche comme nouvelle tâche connexe",
|
||||||
"differentList": "Cette tâche appartient à une autre liste.",
|
"differentList": "Cette tâche appartient à une autre liste.",
|
||||||
|
"differentNamespace": "Cette tâche fait partie d'un espace de noms différent.",
|
||||||
"noneYet": "Pas encore de relations de tâches.",
|
"noneYet": "Pas encore de relations de tâches.",
|
||||||
"delete": "Supprimer la relation de tâche",
|
"delete": "Supprimer la relation de tâche",
|
||||||
"deleteText1": "Supprimer cette relation de tâche ?",
|
"deleteText1": "Supprimer cette relation de tâche ?",
|
||||||
|
@ -743,18 +766,27 @@
|
||||||
},
|
},
|
||||||
"keyboardShortcuts": {
|
"keyboardShortcuts": {
|
||||||
"title": "Raccourcis clavier",
|
"title": "Raccourcis clavier",
|
||||||
|
"general": "Général",
|
||||||
"allPages": "Fonctionne sur toutes les pages.",
|
"allPages": "Fonctionne sur toutes les pages.",
|
||||||
"currentPageOnly": "Fonctionnent uniquement sur la page en cours.",
|
"currentPageOnly": "Fonctionnent uniquement sur la page en cours.",
|
||||||
"toggleMenu": "Basculer le menu",
|
"toggleMenu": "Basculer le menu",
|
||||||
"quickSearch": "Ouvrir la barre de recherche/action rapide",
|
"quickSearch": "Ouvrir la barre de recherche/action rapide",
|
||||||
|
"then": "puis",
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Page de tâche",
|
"title": "Page de tâche",
|
||||||
"done": "Marquer une tâche comme terminée",
|
"done": "Marquer une tâche comme terminée",
|
||||||
"assign": "Assigner cette tâche à un·e utilisateur·rice",
|
"assign": "Assign to a user",
|
||||||
"labels": "Ajouter des étiquettes à cette tâche",
|
"labels": "Ajouter des étiquettes à cette tâche",
|
||||||
"dueDate": "Modifier la date d’échéance de cette tâche",
|
"dueDate": "Modifier la date d’échéance de cette tâche",
|
||||||
"attachment": "Ajouter une pièce jointe à cette tâche",
|
"attachment": "Ajouter une pièce jointe à cette tâche",
|
||||||
"related": "Modifier les tâches connexes de cette tâche"
|
"related": "Modifier les tâches connexes de cette tâche"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "Vues en liste",
|
||||||
|
"switchToListView": "Passer en vue liste",
|
||||||
|
"switchToGanttView": "Passer en vue Gantt",
|
||||||
|
"switchToKanbanView": "Passer en vue kanban",
|
||||||
|
"switchToTableView": "Passer en vue tableau"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
|
@ -776,8 +808,9 @@
|
||||||
"urlPlaceholder": "Par exemple : https://localhost:3456",
|
"urlPlaceholder": "Par exemple : https://localhost:3456",
|
||||||
"change": "changer",
|
"change": "changer",
|
||||||
"signInOn": "Se connecter à ton compte Vikunja sur {0}",
|
"signInOn": "Se connecter à ton compte Vikunja sur {0}",
|
||||||
"error": "Impossible de trouver ou d’utiliser l’installation Vikunja à « {domain} ».",
|
"error": "Impossible de trouver ou d'utiliser l'installation de Vikunja sur « {domain} ». Veuillez essayer une autre URL.",
|
||||||
"success": "Utilisation de l’installation Vikunja à « {domain} »."
|
"success": "Utilisation de l’installation Vikunja à « {domain} ».",
|
||||||
|
"urlRequired": "Une URL est requise."
|
||||||
},
|
},
|
||||||
"loadingError": {
|
"loadingError": {
|
||||||
"failed": "Le chargement a échoué, {0}. Si l’erreur persiste, {1}.",
|
"failed": "Le chargement a échoué, {0}. Si l’erreur persiste, {1}.",
|
||||||
|
@ -792,7 +825,7 @@
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"commands": "Commandes",
|
"commands": "Commandes",
|
||||||
"placeholder": "Écris une commande ou une recherche…",
|
"placeholder": "Écris une commande ou une recherche…",
|
||||||
"hint": "Tu peux utiliser # pour rechercher uniquement les tâches, * pour rechercher uniquement les listes et @ pour rechercher uniquement les équipes.",
|
"hint": "Vous pouvez utiliser {list} pour limiter la recherche à une liste. Combiner {list} ou {label} (étiquettes) avec une requête de recherche pour rechercher une tâche avec ces étiquettes ou sur cette liste. Utilisez {assignee} pour rechercher uniquement des équipes.",
|
||||||
"tasks": "Tâches",
|
"tasks": "Tâches",
|
||||||
"lists": "Listes",
|
"lists": "Listes",
|
||||||
"teams": "Équipes",
|
"teams": "Équipes",
|
||||||
|
|
|
@ -16,6 +16,16 @@
|
||||||
"title": "Non trovato",
|
"title": "Non trovato",
|
||||||
"text": "La pagina richiesta non esiste."
|
"text": "La pagina richiesta non esiste."
|
||||||
},
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Vikunja is loading…",
|
||||||
|
"errorOccured": "An error occured:",
|
||||||
|
"checkApiUrl": "Please check if the api url is correct.",
|
||||||
|
"noApiUrlConfigured": "No API url was configured. Please set one below:"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "You are offline.",
|
||||||
|
"text": "Please check your network connection and try again."
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"username": "Nome utente",
|
"username": "Nome utente",
|
||||||
|
@ -102,10 +112,19 @@
|
||||||
"disabled": "Disabilitato",
|
"disabled": "Disabilitato",
|
||||||
"todoist": "Todoist",
|
"todoist": "Todoist",
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
"title": "Elimina il tuo account Vikunja",
|
"title": "Delete your Vikunja Account",
|
||||||
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, lists, tasks and everything associated with it.",
|
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, lists, tasks and everything associated with it.",
|
||||||
"text2": "Per continuare, inserisci la tua password. Riceverai un'e-mail con ulteriori istruzioni.",
|
"text2": "Per continuare, inserisci la tua password. Riceverai un'e-mail con ulteriori istruzioni.",
|
||||||
"confirm": "Elimina il mio profilo",
|
"confirm": "Elimina il mio profilo",
|
||||||
|
@ -344,6 +363,7 @@
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"title": "Filtri",
|
"title": "Filtri",
|
||||||
|
"clear": "Clear Filters",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"title": "Titolo",
|
"title": "Titolo",
|
||||||
"titlePlaceholder": "Il titolo del filtro salvato va qui…",
|
"titlePlaceholder": "Il titolo del filtro salvato va qui…",
|
||||||
|
@ -449,7 +469,9 @@
|
||||||
"saved": "Salvato!",
|
"saved": "Salvato!",
|
||||||
"default": "Predefinito",
|
"default": "Predefinito",
|
||||||
"close": "Chiudi",
|
"close": "Chiudi",
|
||||||
"download": "Scarica"
|
"download": "Scarica",
|
||||||
|
"showMenu": "Show the menu",
|
||||||
|
"hideMenu": "Hide the menu"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"resetColor": "Ripristina Colore",
|
"resetColor": "Ripristina Colore",
|
||||||
|
@ -506,7 +528,7 @@
|
||||||
"titleDates": "Attività dal {from} al {to}",
|
"titleDates": "Attività dal {from} al {to}",
|
||||||
"noDates": "Mostra attività senza date",
|
"noDates": "Mostra attività senza date",
|
||||||
"current": "Attività attuali",
|
"current": "Attività attuali",
|
||||||
"from": "Attività da",
|
"from": "Tasks from",
|
||||||
"until": "until",
|
"until": "until",
|
||||||
"today": "Oggi",
|
"today": "Oggi",
|
||||||
"nextWeek": "Settimana Prossima",
|
"nextWeek": "Settimana Prossima",
|
||||||
|
@ -534,14 +556,14 @@
|
||||||
"text2": "Questo rimuoverà anche tutti gli allegati, i promemoria e le relazioni associati a questa attività e non può essere ripristinato!"
|
"text2": "Questo rimuoverà anche tutti gli allegati, i promemoria e le relazioni associati a questa attività e non può essere ripristinato!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Assegna questa attività a un utente",
|
"assign": "Assign to a user",
|
||||||
"label": "Aggiungi etichette",
|
"label": "Aggiungi etichette",
|
||||||
"priority": "Imposta Priorità",
|
"priority": "Imposta Priorità",
|
||||||
"dueDate": "Imposta data di scadenza",
|
"dueDate": "Imposta data di scadenza",
|
||||||
"startDate": "Imposta una data di inizio",
|
"startDate": "Imposta una data di inizio",
|
||||||
"endDate": "Imposta una data di fine",
|
"endDate": "Imposta una data di fine",
|
||||||
"reminders": "Imposta promemoria",
|
"reminders": "Imposta promemoria",
|
||||||
"repeatAfter": "Imposta un intervallo di ripetizione",
|
"repeatAfter": "Set a repeating interval",
|
||||||
"percentDone": "Imposta Percentuale Completata",
|
"percentDone": "Imposta Percentuale Completata",
|
||||||
"attachments": "Aggiungi allegati",
|
"attachments": "Aggiungi allegati",
|
||||||
"relatedTasks": "Aggiungi attività collegate",
|
"relatedTasks": "Aggiungi attività collegate",
|
||||||
|
@ -643,6 +665,7 @@
|
||||||
"searchPlaceholder": "Type search for a new task to add as related…",
|
"searchPlaceholder": "Type search for a new task to add as related…",
|
||||||
"createPlaceholder": "Add this as new related task",
|
"createPlaceholder": "Add this as new related task",
|
||||||
"differentList": "This task belongs to a different list.",
|
"differentList": "This task belongs to a different list.",
|
||||||
|
"differentNamespace": "This task belongs to a different namespace.",
|
||||||
"noneYet": "No task relations yet.",
|
"noneYet": "No task relations yet.",
|
||||||
"delete": "Delete Task Relation",
|
"delete": "Delete Task Relation",
|
||||||
"deleteText1": "Are you sure you want to delete this task relation?",
|
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||||
|
@ -743,18 +766,27 @@
|
||||||
},
|
},
|
||||||
"keyboardShortcuts": {
|
"keyboardShortcuts": {
|
||||||
"title": "Keyboard Shortcuts",
|
"title": "Keyboard Shortcuts",
|
||||||
|
"general": "General",
|
||||||
"allPages": "Queste scorciatoie funzionano in tutte le pagine.",
|
"allPages": "Queste scorciatoie funzionano in tutte le pagine.",
|
||||||
"currentPageOnly": "Queste scorciatoie funzionano solo nella pagina attuale.",
|
"currentPageOnly": "Queste scorciatoie funzionano solo nella pagina attuale.",
|
||||||
"toggleMenu": "Attiva/Disattiva Menu",
|
"toggleMenu": "Attiva/Disattiva Menu",
|
||||||
"quickSearch": "Apri la barra di ricerca/azione rapida",
|
"quickSearch": "Apri la barra di ricerca/azione rapida",
|
||||||
|
"then": "then",
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Task Page",
|
"title": "Task Page",
|
||||||
"done": "Mark a task as done",
|
"done": "Mark a task as done",
|
||||||
"assign": "Assign this task to a user",
|
"assign": "Assign to a user",
|
||||||
"labels": "Add labels to this task",
|
"labels": "Add labels to this task",
|
||||||
"dueDate": "Change the due date of this task",
|
"dueDate": "Change the due date of this task",
|
||||||
"attachment": "Add an attachment to this task",
|
"attachment": "Add an attachment to this task",
|
||||||
"related": "Modify related tasks of this task"
|
"related": "Modify related tasks of this task"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "List Views",
|
||||||
|
"switchToListView": "Switch to list view",
|
||||||
|
"switchToGanttView": "Switch to gantt view",
|
||||||
|
"switchToKanbanView": "Switch to kanban view",
|
||||||
|
"switchToTableView": "Switch to table view"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
|
@ -776,8 +808,9 @@
|
||||||
"urlPlaceholder": "es. http://localhost:8080",
|
"urlPlaceholder": "es. http://localhost:8080",
|
||||||
"change": "change",
|
"change": "change",
|
||||||
"signInOn": "Sign in to your Vikunja account on {0}",
|
"signInOn": "Sign in to your Vikunja account on {0}",
|
||||||
"error": "Could not find or use Vikunja installation at \"{domain}\".",
|
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.",
|
||||||
"success": "Using Vikunja installation at \"{domain}\"."
|
"success": "Using Vikunja installation at \"{domain}\".",
|
||||||
|
"urlRequired": "A url is required."
|
||||||
},
|
},
|
||||||
"loadingError": {
|
"loadingError": {
|
||||||
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||||
|
@ -792,7 +825,7 @@
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"commands": "Commands",
|
"commands": "Commands",
|
||||||
"placeholder": "Type a command or search…",
|
"placeholder": "Type a command or search…",
|
||||||
"hint": "You can use # to only seach for tasks, * to only search for lists and @ to only search for teams.",
|
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
|
||||||
"tasks": "Tasks",
|
"tasks": "Tasks",
|
||||||
"lists": "Liste",
|
"lists": "Liste",
|
||||||
"teams": "Teams",
|
"teams": "Teams",
|
||||||
|
|
|
@ -16,6 +16,16 @@
|
||||||
"title": "Not found",
|
"title": "Not found",
|
||||||
"text": "The page you requested does not exist."
|
"text": "The page you requested does not exist."
|
||||||
},
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Vikunja is loading…",
|
||||||
|
"errorOccured": "An error occured:",
|
||||||
|
"checkApiUrl": "Please check if the api url is correct.",
|
||||||
|
"noApiUrlConfigured": "No API url was configured. Please set one below:"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "You are offline.",
|
||||||
|
"text": "Please check your network connection and try again."
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
|
@ -102,6 +112,15 @@
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"todoist": "Todoist",
|
"todoist": "Todoist",
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
|
@ -344,6 +363,7 @@
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"title": "Filters",
|
"title": "Filters",
|
||||||
|
"clear": "Clear Filters",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"titlePlaceholder": "The saved filter title goes here…",
|
"titlePlaceholder": "The saved filter title goes here…",
|
||||||
|
@ -449,7 +469,9 @@
|
||||||
"saved": "Saved!",
|
"saved": "Saved!",
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"download": "Download"
|
"download": "Download",
|
||||||
|
"showMenu": "Show the menu",
|
||||||
|
"hideMenu": "Hide the menu"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"resetColor": "Reset Color",
|
"resetColor": "Reset Color",
|
||||||
|
@ -534,7 +556,7 @@
|
||||||
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
|
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Assign this task to a user",
|
"assign": "Assign to a user",
|
||||||
"label": "Add labels",
|
"label": "Add labels",
|
||||||
"priority": "Set Priority",
|
"priority": "Set Priority",
|
||||||
"dueDate": "Set Due Date",
|
"dueDate": "Set Due Date",
|
||||||
|
@ -643,6 +665,7 @@
|
||||||
"searchPlaceholder": "Type search for a new task to add as related…",
|
"searchPlaceholder": "Type search for a new task to add as related…",
|
||||||
"createPlaceholder": "Add this as new related task",
|
"createPlaceholder": "Add this as new related task",
|
||||||
"differentList": "This task belongs to a different list.",
|
"differentList": "This task belongs to a different list.",
|
||||||
|
"differentNamespace": "This task belongs to a different namespace.",
|
||||||
"noneYet": "No task relations yet.",
|
"noneYet": "No task relations yet.",
|
||||||
"delete": "Delete Task Relation",
|
"delete": "Delete Task Relation",
|
||||||
"deleteText1": "Are you sure you want to delete this task relation?",
|
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||||
|
@ -743,18 +766,27 @@
|
||||||
},
|
},
|
||||||
"keyboardShortcuts": {
|
"keyboardShortcuts": {
|
||||||
"title": "Keyboard Shortcuts",
|
"title": "Keyboard Shortcuts",
|
||||||
|
"general": "General",
|
||||||
"allPages": "These shortcuts work on all pages.",
|
"allPages": "These shortcuts work on all pages.",
|
||||||
"currentPageOnly": "These shortcuts work only on the current page.",
|
"currentPageOnly": "These shortcuts work only on the current page.",
|
||||||
"toggleMenu": "Toggle The Menu",
|
"toggleMenu": "Toggle The Menu",
|
||||||
"quickSearch": "Open the search/quick action bar",
|
"quickSearch": "Open the search/quick action bar",
|
||||||
|
"then": "then",
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Task Page",
|
"title": "Task Page",
|
||||||
"done": "Mark a task as done",
|
"done": "Mark a task as done",
|
||||||
"assign": "Assign this task to a user",
|
"assign": "Assign to a user",
|
||||||
"labels": "Add labels to this task",
|
"labels": "Add labels to this task",
|
||||||
"dueDate": "Change the due date of this task",
|
"dueDate": "Change the due date of this task",
|
||||||
"attachment": "Add an attachment to this task",
|
"attachment": "Add an attachment to this task",
|
||||||
"related": "Modify related tasks of this task"
|
"related": "Modify related tasks of this task"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "List Views",
|
||||||
|
"switchToListView": "Switch to list view",
|
||||||
|
"switchToGanttView": "Switch to gantt view",
|
||||||
|
"switchToKanbanView": "Switch to kanban view",
|
||||||
|
"switchToTableView": "Switch to table view"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
|
@ -776,8 +808,9 @@
|
||||||
"urlPlaceholder": "eg. https://localhost:3456",
|
"urlPlaceholder": "eg. https://localhost:3456",
|
||||||
"change": "change",
|
"change": "change",
|
||||||
"signInOn": "Sign in to your Vikunja account on {0}",
|
"signInOn": "Sign in to your Vikunja account on {0}",
|
||||||
"error": "Could not find or use Vikunja installation at \"{domain}\".",
|
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.",
|
||||||
"success": "Using Vikunja installation at \"{domain}\"."
|
"success": "Using Vikunja installation at \"{domain}\".",
|
||||||
|
"urlRequired": "A url is required."
|
||||||
},
|
},
|
||||||
"loadingError": {
|
"loadingError": {
|
||||||
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||||
|
@ -792,7 +825,7 @@
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"commands": "Commands",
|
"commands": "Commands",
|
||||||
"placeholder": "Type a command or search…",
|
"placeholder": "Type a command or search…",
|
||||||
"hint": "You can use # to only seach for tasks, * to only search for lists and @ to only search for teams.",
|
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
|
||||||
"tasks": "Tasks",
|
"tasks": "Tasks",
|
||||||
"lists": "Lists",
|
"lists": "Lists",
|
||||||
"teams": "Teams",
|
"teams": "Teams",
|
||||||
|
|
|
@ -16,6 +16,16 @@
|
||||||
"title": "Not found",
|
"title": "Not found",
|
||||||
"text": "The page you requested does not exist."
|
"text": "The page you requested does not exist."
|
||||||
},
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Vikunja is loading…",
|
||||||
|
"errorOccured": "An error occured:",
|
||||||
|
"checkApiUrl": "Please check if the api url is correct.",
|
||||||
|
"noApiUrlConfigured": "No API url was configured. Please set one below:"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "You are offline.",
|
||||||
|
"text": "Please check your network connection and try again."
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
|
@ -102,6 +112,15 @@
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"todoist": "Todoist",
|
"todoist": "Todoist",
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
|
@ -344,6 +363,7 @@
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"title": "Filters",
|
"title": "Filters",
|
||||||
|
"clear": "Clear Filters",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"titlePlaceholder": "The saved filter title goes here…",
|
"titlePlaceholder": "The saved filter title goes here…",
|
||||||
|
@ -449,7 +469,9 @@
|
||||||
"saved": "Saved!",
|
"saved": "Saved!",
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"download": "Download"
|
"download": "Download",
|
||||||
|
"showMenu": "Show the menu",
|
||||||
|
"hideMenu": "Hide the menu"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"resetColor": "Reset Color",
|
"resetColor": "Reset Color",
|
||||||
|
@ -534,7 +556,7 @@
|
||||||
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
|
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Assign this task to a user",
|
"assign": "Assign to a user",
|
||||||
"label": "Add labels",
|
"label": "Add labels",
|
||||||
"priority": "Set Priority",
|
"priority": "Set Priority",
|
||||||
"dueDate": "Set Due Date",
|
"dueDate": "Set Due Date",
|
||||||
|
@ -643,6 +665,7 @@
|
||||||
"searchPlaceholder": "Type search for a new task to add as related…",
|
"searchPlaceholder": "Type search for a new task to add as related…",
|
||||||
"createPlaceholder": "Add this as new related task",
|
"createPlaceholder": "Add this as new related task",
|
||||||
"differentList": "This task belongs to a different list.",
|
"differentList": "This task belongs to a different list.",
|
||||||
|
"differentNamespace": "This task belongs to a different namespace.",
|
||||||
"noneYet": "No task relations yet.",
|
"noneYet": "No task relations yet.",
|
||||||
"delete": "Delete Task Relation",
|
"delete": "Delete Task Relation",
|
||||||
"deleteText1": "Are you sure you want to delete this task relation?",
|
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||||
|
@ -743,18 +766,27 @@
|
||||||
},
|
},
|
||||||
"keyboardShortcuts": {
|
"keyboardShortcuts": {
|
||||||
"title": "Keyboard Shortcuts",
|
"title": "Keyboard Shortcuts",
|
||||||
|
"general": "General",
|
||||||
"allPages": "These shortcuts work on all pages.",
|
"allPages": "These shortcuts work on all pages.",
|
||||||
"currentPageOnly": "These shortcuts work only on the current page.",
|
"currentPageOnly": "These shortcuts work only on the current page.",
|
||||||
"toggleMenu": "Toggle The Menu",
|
"toggleMenu": "Toggle The Menu",
|
||||||
"quickSearch": "Open the search/quick action bar",
|
"quickSearch": "Open the search/quick action bar",
|
||||||
|
"then": "then",
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Task Page",
|
"title": "Task Page",
|
||||||
"done": "Mark a task as done",
|
"done": "Mark a task as done",
|
||||||
"assign": "Assign this task to a user",
|
"assign": "Assign to a user",
|
||||||
"labels": "Add labels to this task",
|
"labels": "Add labels to this task",
|
||||||
"dueDate": "Change the due date of this task",
|
"dueDate": "Change the due date of this task",
|
||||||
"attachment": "Add an attachment to this task",
|
"attachment": "Add an attachment to this task",
|
||||||
"related": "Modify related tasks of this task"
|
"related": "Modify related tasks of this task"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "List Views",
|
||||||
|
"switchToListView": "Switch to list view",
|
||||||
|
"switchToGanttView": "Switch to gantt view",
|
||||||
|
"switchToKanbanView": "Switch to kanban view",
|
||||||
|
"switchToTableView": "Switch to table view"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
|
@ -776,8 +808,9 @@
|
||||||
"urlPlaceholder": "eg. https://localhost:3456",
|
"urlPlaceholder": "eg. https://localhost:3456",
|
||||||
"change": "change",
|
"change": "change",
|
||||||
"signInOn": "Sign in to your Vikunja account on {0}",
|
"signInOn": "Sign in to your Vikunja account on {0}",
|
||||||
"error": "Could not find or use Vikunja installation at \"{domain}\".",
|
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.",
|
||||||
"success": "Using Vikunja installation at \"{domain}\"."
|
"success": "Using Vikunja installation at \"{domain}\".",
|
||||||
|
"urlRequired": "A url is required."
|
||||||
},
|
},
|
||||||
"loadingError": {
|
"loadingError": {
|
||||||
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||||
|
@ -792,7 +825,7 @@
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"commands": "Commands",
|
"commands": "Commands",
|
||||||
"placeholder": "Type a command or search…",
|
"placeholder": "Type a command or search…",
|
||||||
"hint": "You can use # to only seach for tasks, * to only search for lists and @ to only search for teams.",
|
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
|
||||||
"tasks": "Tasks",
|
"tasks": "Tasks",
|
||||||
"lists": "Lists",
|
"lists": "Lists",
|
||||||
"teams": "Teams",
|
"teams": "Teams",
|
||||||
|
|
|
@ -16,6 +16,16 @@
|
||||||
"title": "Not found",
|
"title": "Not found",
|
||||||
"text": "The page you requested does not exist."
|
"text": "The page you requested does not exist."
|
||||||
},
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Vikunja is loading…",
|
||||||
|
"errorOccured": "An error occured:",
|
||||||
|
"checkApiUrl": "Please check if the api url is correct.",
|
||||||
|
"noApiUrlConfigured": "No API url was configured. Please set one below:"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "You are offline.",
|
||||||
|
"text": "Please check your network connection and try again."
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
|
@ -102,6 +112,15 @@
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"todoist": "Todoist",
|
"todoist": "Todoist",
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
|
@ -344,6 +363,7 @@
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"title": "Filters",
|
"title": "Filters",
|
||||||
|
"clear": "Clear Filters",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"titlePlaceholder": "The saved filter title goes here…",
|
"titlePlaceholder": "The saved filter title goes here…",
|
||||||
|
@ -449,7 +469,9 @@
|
||||||
"saved": "Saved!",
|
"saved": "Saved!",
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"download": "Download"
|
"download": "Download",
|
||||||
|
"showMenu": "Show the menu",
|
||||||
|
"hideMenu": "Hide the menu"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"resetColor": "Reset Color",
|
"resetColor": "Reset Color",
|
||||||
|
@ -534,7 +556,7 @@
|
||||||
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
|
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Assign this task to a user",
|
"assign": "Assign to a user",
|
||||||
"label": "Add labels",
|
"label": "Add labels",
|
||||||
"priority": "Set Priority",
|
"priority": "Set Priority",
|
||||||
"dueDate": "Set Due Date",
|
"dueDate": "Set Due Date",
|
||||||
|
@ -643,6 +665,7 @@
|
||||||
"searchPlaceholder": "Type search for a new task to add as related…",
|
"searchPlaceholder": "Type search for a new task to add as related…",
|
||||||
"createPlaceholder": "Add this as new related task",
|
"createPlaceholder": "Add this as new related task",
|
||||||
"differentList": "This task belongs to a different list.",
|
"differentList": "This task belongs to a different list.",
|
||||||
|
"differentNamespace": "This task belongs to a different namespace.",
|
||||||
"noneYet": "No task relations yet.",
|
"noneYet": "No task relations yet.",
|
||||||
"delete": "Delete Task Relation",
|
"delete": "Delete Task Relation",
|
||||||
"deleteText1": "Are you sure you want to delete this task relation?",
|
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||||
|
@ -743,18 +766,27 @@
|
||||||
},
|
},
|
||||||
"keyboardShortcuts": {
|
"keyboardShortcuts": {
|
||||||
"title": "Keyboard Shortcuts",
|
"title": "Keyboard Shortcuts",
|
||||||
|
"general": "General",
|
||||||
"allPages": "These shortcuts work on all pages.",
|
"allPages": "These shortcuts work on all pages.",
|
||||||
"currentPageOnly": "These shortcuts work only on the current page.",
|
"currentPageOnly": "These shortcuts work only on the current page.",
|
||||||
"toggleMenu": "Toggle The Menu",
|
"toggleMenu": "Toggle The Menu",
|
||||||
"quickSearch": "Open the search/quick action bar",
|
"quickSearch": "Open the search/quick action bar",
|
||||||
|
"then": "then",
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Task Page",
|
"title": "Task Page",
|
||||||
"done": "Mark a task as done",
|
"done": "Mark a task as done",
|
||||||
"assign": "Assign this task to a user",
|
"assign": "Assign to a user",
|
||||||
"labels": "Add labels to this task",
|
"labels": "Add labels to this task",
|
||||||
"dueDate": "Change the due date of this task",
|
"dueDate": "Change the due date of this task",
|
||||||
"attachment": "Add an attachment to this task",
|
"attachment": "Add an attachment to this task",
|
||||||
"related": "Modify related tasks of this task"
|
"related": "Modify related tasks of this task"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "List Views",
|
||||||
|
"switchToListView": "Switch to list view",
|
||||||
|
"switchToGanttView": "Switch to gantt view",
|
||||||
|
"switchToKanbanView": "Switch to kanban view",
|
||||||
|
"switchToTableView": "Switch to table view"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
|
@ -776,8 +808,9 @@
|
||||||
"urlPlaceholder": "eg. https://localhost:3456",
|
"urlPlaceholder": "eg. https://localhost:3456",
|
||||||
"change": "change",
|
"change": "change",
|
||||||
"signInOn": "Sign in to your Vikunja account on {0}",
|
"signInOn": "Sign in to your Vikunja account on {0}",
|
||||||
"error": "Could not find or use Vikunja installation at \"{domain}\".",
|
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.",
|
||||||
"success": "Using Vikunja installation at \"{domain}\"."
|
"success": "Using Vikunja installation at \"{domain}\".",
|
||||||
|
"urlRequired": "A url is required."
|
||||||
},
|
},
|
||||||
"loadingError": {
|
"loadingError": {
|
||||||
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||||
|
@ -792,7 +825,7 @@
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"commands": "Commands",
|
"commands": "Commands",
|
||||||
"placeholder": "Type a command or search…",
|
"placeholder": "Type a command or search…",
|
||||||
"hint": "You can use # to only seach for tasks, * to only search for lists and @ to only search for teams.",
|
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
|
||||||
"tasks": "Tasks",
|
"tasks": "Tasks",
|
||||||
"lists": "Lists",
|
"lists": "Lists",
|
||||||
"teams": "Teams",
|
"teams": "Teams",
|
||||||
|
|
|
@ -16,6 +16,16 @@
|
||||||
"title": "Not found",
|
"title": "Not found",
|
||||||
"text": "The page you requested does not exist."
|
"text": "The page you requested does not exist."
|
||||||
},
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Vikunja is loading…",
|
||||||
|
"errorOccured": "An error occured:",
|
||||||
|
"checkApiUrl": "Please check if the api url is correct.",
|
||||||
|
"noApiUrlConfigured": "No API url was configured. Please set one below:"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "You are offline.",
|
||||||
|
"text": "Please check your network connection and try again."
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
|
@ -102,6 +112,15 @@
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"todoist": "Todoist",
|
"todoist": "Todoist",
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
|
@ -344,6 +363,7 @@
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"title": "Filters",
|
"title": "Filters",
|
||||||
|
"clear": "Clear Filters",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"titlePlaceholder": "The saved filter title goes here…",
|
"titlePlaceholder": "The saved filter title goes here…",
|
||||||
|
@ -449,7 +469,9 @@
|
||||||
"saved": "Saved!",
|
"saved": "Saved!",
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"download": "Download"
|
"download": "Download",
|
||||||
|
"showMenu": "Show the menu",
|
||||||
|
"hideMenu": "Hide the menu"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"resetColor": "Reset Color",
|
"resetColor": "Reset Color",
|
||||||
|
@ -534,7 +556,7 @@
|
||||||
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
|
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Assign this task to a user",
|
"assign": "Assign to a user",
|
||||||
"label": "Add labels",
|
"label": "Add labels",
|
||||||
"priority": "Set Priority",
|
"priority": "Set Priority",
|
||||||
"dueDate": "Set Due Date",
|
"dueDate": "Set Due Date",
|
||||||
|
@ -643,6 +665,7 @@
|
||||||
"searchPlaceholder": "Type search for a new task to add as related…",
|
"searchPlaceholder": "Type search for a new task to add as related…",
|
||||||
"createPlaceholder": "Add this as new related task",
|
"createPlaceholder": "Add this as new related task",
|
||||||
"differentList": "This task belongs to a different list.",
|
"differentList": "This task belongs to a different list.",
|
||||||
|
"differentNamespace": "This task belongs to a different namespace.",
|
||||||
"noneYet": "No task relations yet.",
|
"noneYet": "No task relations yet.",
|
||||||
"delete": "Delete Task Relation",
|
"delete": "Delete Task Relation",
|
||||||
"deleteText1": "Are you sure you want to delete this task relation?",
|
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||||
|
@ -743,18 +766,27 @@
|
||||||
},
|
},
|
||||||
"keyboardShortcuts": {
|
"keyboardShortcuts": {
|
||||||
"title": "Keyboard Shortcuts",
|
"title": "Keyboard Shortcuts",
|
||||||
|
"general": "General",
|
||||||
"allPages": "These shortcuts work on all pages.",
|
"allPages": "These shortcuts work on all pages.",
|
||||||
"currentPageOnly": "These shortcuts work only on the current page.",
|
"currentPageOnly": "These shortcuts work only on the current page.",
|
||||||
"toggleMenu": "Toggle The Menu",
|
"toggleMenu": "Toggle The Menu",
|
||||||
"quickSearch": "Open the search/quick action bar",
|
"quickSearch": "Open the search/quick action bar",
|
||||||
|
"then": "then",
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Task Page",
|
"title": "Task Page",
|
||||||
"done": "Mark a task as done",
|
"done": "Mark a task as done",
|
||||||
"assign": "Assign this task to a user",
|
"assign": "Assign to a user",
|
||||||
"labels": "Add labels to this task",
|
"labels": "Add labels to this task",
|
||||||
"dueDate": "Change the due date of this task",
|
"dueDate": "Change the due date of this task",
|
||||||
"attachment": "Add an attachment to this task",
|
"attachment": "Add an attachment to this task",
|
||||||
"related": "Modify related tasks of this task"
|
"related": "Modify related tasks of this task"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "List Views",
|
||||||
|
"switchToListView": "Switch to list view",
|
||||||
|
"switchToGanttView": "Switch to gantt view",
|
||||||
|
"switchToKanbanView": "Switch to kanban view",
|
||||||
|
"switchToTableView": "Switch to table view"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
|
@ -776,8 +808,9 @@
|
||||||
"urlPlaceholder": "eg. https://localhost:3456",
|
"urlPlaceholder": "eg. https://localhost:3456",
|
||||||
"change": "change",
|
"change": "change",
|
||||||
"signInOn": "Sign in to your Vikunja account on {0}",
|
"signInOn": "Sign in to your Vikunja account on {0}",
|
||||||
"error": "Could not find or use Vikunja installation at \"{domain}\".",
|
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.",
|
||||||
"success": "Using Vikunja installation at \"{domain}\"."
|
"success": "Using Vikunja installation at \"{domain}\".",
|
||||||
|
"urlRequired": "A url is required."
|
||||||
},
|
},
|
||||||
"loadingError": {
|
"loadingError": {
|
||||||
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||||
|
@ -792,7 +825,7 @@
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"commands": "Commands",
|
"commands": "Commands",
|
||||||
"placeholder": "Type a command or search…",
|
"placeholder": "Type a command or search…",
|
||||||
"hint": "You can use # to only seach for tasks, * to only search for lists and @ to only search for teams.",
|
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
|
||||||
"tasks": "Tasks",
|
"tasks": "Tasks",
|
||||||
"lists": "Lists",
|
"lists": "Lists",
|
||||||
"teams": "Teams",
|
"teams": "Teams",
|
||||||
|
|
|
@ -16,6 +16,16 @@
|
||||||
"title": "Не найдено",
|
"title": "Не найдено",
|
||||||
"text": "Запрашиваемая страница не существует."
|
"text": "Запрашиваемая страница не существует."
|
||||||
},
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Vikunja is loading…",
|
||||||
|
"errorOccured": "An error occured:",
|
||||||
|
"checkApiUrl": "Please check if the api url is correct.",
|
||||||
|
"noApiUrlConfigured": "No API url was configured. Please set one below:"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "You are offline.",
|
||||||
|
"text": "Please check your network connection and try again."
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"username": "Имя пользователя",
|
"username": "Имя пользователя",
|
||||||
|
@ -102,6 +112,15 @@
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"todoist": "Todoist",
|
"todoist": "Todoist",
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
|
@ -344,6 +363,7 @@
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"title": "Фильтры",
|
"title": "Фильтры",
|
||||||
|
"clear": "Clear Filters",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"title": "Название",
|
"title": "Название",
|
||||||
"titlePlaceholder": "Введи название сохранённого фильтра…",
|
"titlePlaceholder": "Введи название сохранённого фильтра…",
|
||||||
|
@ -449,7 +469,9 @@
|
||||||
"saved": "Сохранено!",
|
"saved": "Сохранено!",
|
||||||
"default": "По умолчанию",
|
"default": "По умолчанию",
|
||||||
"close": "Закрыть",
|
"close": "Закрыть",
|
||||||
"download": "Скачать"
|
"download": "Скачать",
|
||||||
|
"showMenu": "Show the menu",
|
||||||
|
"hideMenu": "Hide the menu"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"resetColor": "Сбросить цвет",
|
"resetColor": "Сбросить цвет",
|
||||||
|
@ -534,7 +556,7 @@
|
||||||
"text2": "Будут удалены все вложения, напоминания и отношения, связанные с этой задачей, и отменить это будет нельзя!"
|
"text2": "Будут удалены все вложения, напоминания и отношения, связанные с этой задачей, и отменить это будет нельзя!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Назначить пользователю",
|
"assign": "Assign to a user",
|
||||||
"label": "Добавить метки",
|
"label": "Добавить метки",
|
||||||
"priority": "Установить приоритет",
|
"priority": "Установить приоритет",
|
||||||
"dueDate": "Установить срок",
|
"dueDate": "Установить срок",
|
||||||
|
@ -643,6 +665,7 @@
|
||||||
"searchPlaceholder": "Введи запрос для поиска задачи, чтобы добавить связь…",
|
"searchPlaceholder": "Введи запрос для поиска задачи, чтобы добавить связь…",
|
||||||
"createPlaceholder": "Добавить как связанную задачу",
|
"createPlaceholder": "Добавить как связанную задачу",
|
||||||
"differentList": "Эта задача принадлежит другому списку.",
|
"differentList": "Эта задача принадлежит другому списку.",
|
||||||
|
"differentNamespace": "This task belongs to a different namespace.",
|
||||||
"noneYet": "Ещё нет связанных задач.",
|
"noneYet": "Ещё нет связанных задач.",
|
||||||
"delete": "Удалить связь",
|
"delete": "Удалить связь",
|
||||||
"deleteText1": "Удалить эту связь с задачей?",
|
"deleteText1": "Удалить эту связь с задачей?",
|
||||||
|
@ -743,18 +766,27 @@
|
||||||
},
|
},
|
||||||
"keyboardShortcuts": {
|
"keyboardShortcuts": {
|
||||||
"title": "Сочетания клавиш",
|
"title": "Сочетания клавиш",
|
||||||
|
"general": "General",
|
||||||
"allPages": "Работают на всех страницах.",
|
"allPages": "Работают на всех страницах.",
|
||||||
"currentPageOnly": "Работают только на текущей странице.",
|
"currentPageOnly": "Работают только на текущей странице.",
|
||||||
"toggleMenu": "Переключить меню",
|
"toggleMenu": "Переключить меню",
|
||||||
"quickSearch": "Открыть панель поиска/быстрых действий",
|
"quickSearch": "Открыть панель поиска/быстрых действий",
|
||||||
|
"then": "then",
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Страница задачи",
|
"title": "Страница задачи",
|
||||||
"done": "Пометить задачу завершённой",
|
"done": "Пометить задачу завершённой",
|
||||||
"assign": "Назначить задачу пользователю",
|
"assign": "Assign to a user",
|
||||||
"labels": "Добавить метки этой задаче",
|
"labels": "Добавить метки этой задаче",
|
||||||
"dueDate": "Изменить срок этой задачи",
|
"dueDate": "Изменить срок этой задачи",
|
||||||
"attachment": "Добавить вложение к задаче",
|
"attachment": "Добавить вложение к задаче",
|
||||||
"related": "Изменить связанные задачи"
|
"related": "Изменить связанные задачи"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "List Views",
|
||||||
|
"switchToListView": "Switch to list view",
|
||||||
|
"switchToGanttView": "Switch to gantt view",
|
||||||
|
"switchToKanbanView": "Switch to kanban view",
|
||||||
|
"switchToTableView": "Switch to table view"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
|
@ -776,8 +808,9 @@
|
||||||
"urlPlaceholder": "напр. https://localhost:3456",
|
"urlPlaceholder": "напр. https://localhost:3456",
|
||||||
"change": "изменить",
|
"change": "изменить",
|
||||||
"signInOn": "Войди в свой аккаунт Vikunja на {0}",
|
"signInOn": "Войди в свой аккаунт Vikunja на {0}",
|
||||||
"error": "Не удалось найти или использовать Vikunja на \"{domain}\".",
|
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.",
|
||||||
"success": "Используется Vikunja на \"{domain}\"."
|
"success": "Используется Vikunja на \"{domain}\".",
|
||||||
|
"urlRequired": "A url is required."
|
||||||
},
|
},
|
||||||
"loadingError": {
|
"loadingError": {
|
||||||
"failed": "Не удалось загрузить, пожалуйста, {0}. Если ошибка повторится, {1}.",
|
"failed": "Не удалось загрузить, пожалуйста, {0}. Если ошибка повторится, {1}.",
|
||||||
|
@ -792,7 +825,7 @@
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"commands": "Команды",
|
"commands": "Команды",
|
||||||
"placeholder": "Введи команду или поисковый запрос…",
|
"placeholder": "Введи команду или поисковый запрос…",
|
||||||
"hint": "Используй # для поиска только задач, * для поиска только списков и @ для поиска только команд.",
|
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
|
||||||
"tasks": "Задачи",
|
"tasks": "Задачи",
|
||||||
"lists": "Списки",
|
"lists": "Списки",
|
||||||
"teams": "Команды",
|
"teams": "Команды",
|
||||||
|
|
|
@ -0,0 +1,933 @@
|
||||||
|
{
|
||||||
|
"home": {
|
||||||
|
"welcomeNight": "Good Night {username}",
|
||||||
|
"welcomeMorning": "Good Morning {username}",
|
||||||
|
"welcomeDay": "Hi {username}",
|
||||||
|
"welcomeEvening": "Good Evening {username}",
|
||||||
|
"lastViewed": "Last viewed",
|
||||||
|
"list": {
|
||||||
|
"newText": "You can create a new list for your new tasks:",
|
||||||
|
"new": "Create a new list",
|
||||||
|
"importText": "Or import your lists and tasks from other services into Vikunja:",
|
||||||
|
"import": "Import your data into Vikunja"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"title": "Not found",
|
||||||
|
"text": "The page you requested does not exist."
|
||||||
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Vikunja is loading…",
|
||||||
|
"errorOccured": "An error occured:",
|
||||||
|
"checkApiUrl": "Please check if the api url is correct.",
|
||||||
|
"noApiUrlConfigured": "No API url was configured. Please set one below:"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "You are offline.",
|
||||||
|
"text": "Please check your network connection and try again."
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"auth": {
|
||||||
|
"username": "Username",
|
||||||
|
"usernameEmail": "Username Or Email Address",
|
||||||
|
"usernamePlaceholder": "e.g. frederick",
|
||||||
|
"email": "E-mail address",
|
||||||
|
"emailPlaceholder": "e.g. frederic{'@'}vikunja.io",
|
||||||
|
"password": "Password",
|
||||||
|
"passwordRepeat": "Retype your password",
|
||||||
|
"passwordPlaceholder": "e.g. •••••••••••",
|
||||||
|
"resetPassword": "Reset your password",
|
||||||
|
"resetPasswordAction": "Send me a password reset link",
|
||||||
|
"resetPasswordSuccess": "Check your inbox! You should have an e-mail with instructions on how to reset your password.",
|
||||||
|
"passwordsDontMatch": "Passwords don't match",
|
||||||
|
"confirmEmailSuccess": "You successfully confirmed your email! You can log in now.",
|
||||||
|
"totpTitle": "Two Factor Authentication Code",
|
||||||
|
"totpPlaceholder": "e.g. 123456",
|
||||||
|
"login": "Login",
|
||||||
|
"register": "Register",
|
||||||
|
"loginWith": "Log in with {provider}",
|
||||||
|
"authenticating": "Authenticating…",
|
||||||
|
"openIdStateError": "State does not match, refusing to continue!",
|
||||||
|
"openIdGeneralError": "An error occured while authenticating against the third party.",
|
||||||
|
"logout": "Logout"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "Settings",
|
||||||
|
"newPasswordTitle": "Update Your Password",
|
||||||
|
"newPassword": "New Password",
|
||||||
|
"newPasswordConfirm": "New Password Confirmation",
|
||||||
|
"currentPassword": "Current Password",
|
||||||
|
"currentPasswordPlaceholder": "Your current password",
|
||||||
|
"passwordsDontMatch": "The new password and its confirmation don't match.",
|
||||||
|
"passwordUpdateSuccess": "The password was successfully updated.",
|
||||||
|
"updateEmailTitle": "Update Your E-Mail Address",
|
||||||
|
"updateEmailNew": "New Email Address",
|
||||||
|
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
|
||||||
|
"general": {
|
||||||
|
"title": "General Settings",
|
||||||
|
"name": "Name",
|
||||||
|
"newName": "The new Name",
|
||||||
|
"savedSuccess": "The settings were successfully updated.",
|
||||||
|
"emailReminders": "Send me reminders for tasks via Email",
|
||||||
|
"overdueReminders": "Send me reminders for overdue undone tasks via email each morning",
|
||||||
|
"discoverableByName": "Let other users find me when they search for my name",
|
||||||
|
"discoverableByEmail": "Let other users find me when they search for my full email",
|
||||||
|
"playSoundWhenDone": "Play a sound when marking tasks as done",
|
||||||
|
"weekStart": "Week starts on",
|
||||||
|
"weekStartSunday": "Sunday",
|
||||||
|
"weekStartMonday": "Monday",
|
||||||
|
"language": "Language",
|
||||||
|
"defaultList": "Default List"
|
||||||
|
},
|
||||||
|
"totp": {
|
||||||
|
"title": "Two Factor Authentication",
|
||||||
|
"enroll": "Enroll",
|
||||||
|
"finishSetupPart1": "To finish your setup, use this secret in your totp app (Google Authenticator or similar):",
|
||||||
|
"finishSetupPart2": "After that, enter a code from your app below.",
|
||||||
|
"scanQR": "Alternatively you can scan this QR code:",
|
||||||
|
"passcode": "Passcode",
|
||||||
|
"passcodePlaceholder": "A code generated by your totp application",
|
||||||
|
"setupSuccess": "You've sucessfully set up two factor authentication!",
|
||||||
|
"enterPassword": "Please Enter Your Password",
|
||||||
|
"disable": "Disable two factor authentication",
|
||||||
|
"confirmSuccess": "You've successfully confirmed your totp setup and can use it from now on!",
|
||||||
|
"disableSuccess": "Two factor authentication was sucessfully disabled."
|
||||||
|
},
|
||||||
|
"caldav": {
|
||||||
|
"title": "Caldav",
|
||||||
|
"howTo": "You can connect Vikunja to caldav clients to view and manage all tasks from different clients. Enter this url into your client:",
|
||||||
|
"more": "More information about caldav in Vikunja"
|
||||||
|
},
|
||||||
|
"avatar": {
|
||||||
|
"title": "Avatar",
|
||||||
|
"initials": "Initials",
|
||||||
|
"gravatar": "Gravatar",
|
||||||
|
"upload": "Upload",
|
||||||
|
"uploadAvatar": "Upload Avatar",
|
||||||
|
"statusUpdateSuccess": "Avatar status was updated successfully!",
|
||||||
|
"setSuccess": "The avatar has been set successfully!"
|
||||||
|
},
|
||||||
|
"quickAddMagic": {
|
||||||
|
"title": "Quick Add Magic Mode",
|
||||||
|
"disabled": "Disabled",
|
||||||
|
"todoist": "Todoist",
|
||||||
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deletion": {
|
||||||
|
"title": "Delete your Vikunja Account",
|
||||||
|
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, lists, tasks and everything associated with it.",
|
||||||
|
"text2": "To proceed, please enter your password. You will receive an email with further instructions.",
|
||||||
|
"confirm": "Delete my account",
|
||||||
|
"requestSuccess": "The request was successful. You'll receive an email with further instructions.",
|
||||||
|
"passwordRequired": "Please enter your password.",
|
||||||
|
"confirmSuccess": "You've successfully confirmed the deletion of your account. We will delete your account in three days.",
|
||||||
|
"scheduled": "We will delete your Vikunja account at {date} ({dateSince}).",
|
||||||
|
"scheduledCancel": "To cancel the deletion of your account, click here.",
|
||||||
|
"scheduledCancelText": "To cancel the deletion of your account, please enter your password below:",
|
||||||
|
"scheduledCancelConfirm": "Cancel the deletion of my account",
|
||||||
|
"scheduledCancelSuccess": "We will not delete your account."
|
||||||
|
},
|
||||||
|
"export": {
|
||||||
|
"title": "Export your Vikunja data",
|
||||||
|
"description": "You can request a copy of all your Vikunja data. This include Namespaces, Lists, Tasks and everything associated to them. You can import this data in any Vikunja instance through the migration function.",
|
||||||
|
"descriptionPasswordRequired": "Please enter your password to proceed:",
|
||||||
|
"request": "Request a copy of my Vikunja Data",
|
||||||
|
"success": "You've successfully requested your Vikunja Data! We will send you an email once it's ready to download.",
|
||||||
|
"downloadTitle": "Download your exported Vikunja data"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"archived": "This list is archived. It is not possible to create new or edit tasks for it.",
|
||||||
|
"title": "List Title",
|
||||||
|
"color": "Color",
|
||||||
|
"lists": "Lists",
|
||||||
|
"search": "Type to search for a list…",
|
||||||
|
"searchSelect": "Click or press enter to select this list",
|
||||||
|
"shared": "Shared Lists",
|
||||||
|
"create": {
|
||||||
|
"header": "Create a new list",
|
||||||
|
"titlePlaceholder": "The list's title goes here…",
|
||||||
|
"addTitleRequired": "Please specify a title.",
|
||||||
|
"createdSuccess": "The list was successfully created.",
|
||||||
|
"addListRequired": "Please specify a list or set a default list in the settings."
|
||||||
|
},
|
||||||
|
"archive": {
|
||||||
|
"title": "Archive \"{list}\"",
|
||||||
|
"archive": "Archive this list",
|
||||||
|
"unarchive": "Un-Archive this list",
|
||||||
|
"unarchiveText": "You will be able to create new tasks or edit it.",
|
||||||
|
"archiveText": "You won't be able to edit this list or create new tasks until you un-archive it.",
|
||||||
|
"success": "The list was successfully archived."
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"title": "Set list background",
|
||||||
|
"remove": "Remove Background",
|
||||||
|
"upload": "Choose a background from your pc",
|
||||||
|
"searchPlaceholder": "Search for a background…",
|
||||||
|
"poweredByUnsplash": "Powered by Unsplash",
|
||||||
|
"loadMore": "Load more photos",
|
||||||
|
"success": "The background has been set successfully!",
|
||||||
|
"removeSuccess": "The background has been removed successfully!"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"title": "Delete \"{list}\"",
|
||||||
|
"header": "Delete this list",
|
||||||
|
"text1": "Are you sure you want to delete this list and all of its contents?",
|
||||||
|
"text2": "This includes all tasks and CANNOT BE UNDONE!",
|
||||||
|
"success": "The list was successfully deleted."
|
||||||
|
},
|
||||||
|
"duplicate": {
|
||||||
|
"title": "Duplicate this list",
|
||||||
|
"label": "Duplicate",
|
||||||
|
"text": "Select a namespace which should hold the duplicated list:",
|
||||||
|
"success": "The list was successfully duplicated."
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"header": "Edit This List",
|
||||||
|
"title": "Edit \"{list}\"",
|
||||||
|
"titlePlaceholder": "The list title goes here…",
|
||||||
|
"identifierTooltip": "The list identifier can be used to uniquely identify a task across lists. You can set it to empty to disable it.",
|
||||||
|
"identifier": "List Identifier",
|
||||||
|
"identifierPlaceholder": "The list identifier goes here…",
|
||||||
|
"description": "Description",
|
||||||
|
"descriptionPlaceholder": "The lists description goes here…",
|
||||||
|
"color": "Color",
|
||||||
|
"success": "The list was successfully updated."
|
||||||
|
},
|
||||||
|
"share": {
|
||||||
|
"header": "Share this list",
|
||||||
|
"title": "Share \"{list}\"",
|
||||||
|
"share": "Share",
|
||||||
|
"links": {
|
||||||
|
"title": "Share Links",
|
||||||
|
"what": "What is a share link?",
|
||||||
|
"explanation": "Share Links allow you to easily share a list with other users who don't have an account on Vikunja.",
|
||||||
|
"create": "Create a new link share",
|
||||||
|
"name": "Name (optional)",
|
||||||
|
"namePlaceholder": "e.g. Lorem Ipsum",
|
||||||
|
"nameExplanation": "All actions done by this link share will show up with the name.",
|
||||||
|
"password": "Password (optional)",
|
||||||
|
"passwordExplanation": "When authenticating, the user will be required to enter this password.",
|
||||||
|
"noName": "No name set",
|
||||||
|
"remove": "Remove a link share",
|
||||||
|
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this list with this link share. This cannot be undone!",
|
||||||
|
"createSuccess": "The link share was successfully created.",
|
||||||
|
"deleteSuccess": "The link share was successfully deleted"
|
||||||
|
},
|
||||||
|
"userTeam": {
|
||||||
|
"typeUser": "user | users",
|
||||||
|
"typeTeam": "team | teams",
|
||||||
|
"shared": "Shared with these {type}",
|
||||||
|
"you": "You",
|
||||||
|
"notShared": "Not shared with any {type} yet.",
|
||||||
|
"removeHeader": "Remove a {type} from the {sharable}",
|
||||||
|
"removeText": "Are you sure you want to remove this {sharable} from the {type}? This cannot be undone!",
|
||||||
|
"removeSuccess": "The {sharable} was successfully removed from the {type}.",
|
||||||
|
"addedSuccess": "The {type} was successfully added.",
|
||||||
|
"updatedSuccess": "The {type} was successfully added."
|
||||||
|
},
|
||||||
|
"right": {
|
||||||
|
"title": "Right",
|
||||||
|
"read": "Read only",
|
||||||
|
"readWrite": "Read & write",
|
||||||
|
"admin": "Admin"
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"link": "Link",
|
||||||
|
"name": "Name",
|
||||||
|
"sharedBy": "Shared by",
|
||||||
|
"right": "Right",
|
||||||
|
"delete": "Delete"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "List",
|
||||||
|
"add": "Add",
|
||||||
|
"addPlaceholder": "Add a new task…",
|
||||||
|
"empty": "This list is currently empty.",
|
||||||
|
"newTaskCta": "Create a new task.",
|
||||||
|
"editTask": "Edit Task"
|
||||||
|
},
|
||||||
|
"gantt": {
|
||||||
|
"title": "Gantt",
|
||||||
|
"showTasksWithoutDates": "Show tasks which don't have dates set",
|
||||||
|
"size": "Size",
|
||||||
|
"default": "Default",
|
||||||
|
"month": "Month",
|
||||||
|
"day": "Day",
|
||||||
|
"from": "From",
|
||||||
|
"to": "To",
|
||||||
|
"noDates": "This task has no dates set."
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"title": "Table",
|
||||||
|
"columns": "Columns"
|
||||||
|
},
|
||||||
|
"kanban": {
|
||||||
|
"title": "Kanban",
|
||||||
|
"limit": "Limit: {limit}",
|
||||||
|
"noLimit": "Not Set",
|
||||||
|
"doneBucket": "Done bucket",
|
||||||
|
"doneBucketHint": "All tasks moved into this bucket will automatically marked as done.",
|
||||||
|
"doneBucketHintExtended": "All tasks moved into the done bucket will be marked as done automatically. All tasks marked as done from elsewhere will be moved as well.",
|
||||||
|
"doneBucketSavedSuccess": "The done bucket has been saved successfully.",
|
||||||
|
"deleteLast": "You cannot remove the last bucket.",
|
||||||
|
"addTaskPlaceholder": "Enter the new task title…",
|
||||||
|
"addTask": "Add a task",
|
||||||
|
"addAnotherTask": "Add another task",
|
||||||
|
"addBucket": "Create a new bucket",
|
||||||
|
"addBucketPlaceholder": "Enter the new bucket title…",
|
||||||
|
"deleteHeaderBucket": "Delete the bucket",
|
||||||
|
"deleteBucketText1": "Are you sure you want to delete this bucket?",
|
||||||
|
"deleteBucketText2": "This will not delete any tasks but move them into the default bucket.",
|
||||||
|
"deleteBucketSuccess": "The bucket has been deleted successfully.",
|
||||||
|
"bucketTitleSavedSuccess": "The bucket title has been saved successfully.",
|
||||||
|
"bucketLimitSavedSuccess": "The bucket limit been saved successfully.",
|
||||||
|
"collapse": "Collapse this bucket"
|
||||||
|
},
|
||||||
|
"pseudo": {
|
||||||
|
"favorites": {
|
||||||
|
"title": "Favorites"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"namespace": {
|
||||||
|
"title": "Namespaces & Lists",
|
||||||
|
"namespace": "Namespace",
|
||||||
|
"showArchived": "Show Archived",
|
||||||
|
"noneAvailable": "You don't have any namespaces right now.",
|
||||||
|
"unarchive": "Un-Archive",
|
||||||
|
"archived": "Archived",
|
||||||
|
"noLists": "This namespace does not contain any lists.",
|
||||||
|
"createList": "Create a new list in this namespace.",
|
||||||
|
"namespaces": "Namespaces",
|
||||||
|
"search": "Type to search for a namespace…",
|
||||||
|
"create": {
|
||||||
|
"title": "Create a new namespace",
|
||||||
|
"titleRequired": "Please specify a title.",
|
||||||
|
"explanation": "A namespace is a collection of lists you can share and use to organize your lists with. In fact, every list belongs to a namepace.",
|
||||||
|
"tooltip": "What's a namespace?",
|
||||||
|
"success": "The namespace was successfully created."
|
||||||
|
},
|
||||||
|
"archive": {
|
||||||
|
"titleArchive": "Archive \"{namespace}\"",
|
||||||
|
"titleUnarchive": "Un-Archive \"{namespace}\"",
|
||||||
|
"archiveText": "You won't be able to edit this namespace or create new lists until you un-archive it. This will also archive all lists in this namespace.",
|
||||||
|
"unarchiveText": "You will be able to create new lists or edit it.",
|
||||||
|
"success": "The namespace was successfully archived.",
|
||||||
|
"description": "If a namespace is archived, you cannot create new lists or edit it."
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"title": "Delete \"{namespace}\"",
|
||||||
|
"text1": "Are you sure you want to delete this namespace and all of its contents?",
|
||||||
|
"text2": "This includes all lists and tasks and CANNOT BE UNDONE!",
|
||||||
|
"success": "The namespace was successfully deleted."
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"title": "Edit \"{namespace}\"",
|
||||||
|
"success": "The namespace was successfully updated."
|
||||||
|
},
|
||||||
|
"share": {
|
||||||
|
"title": "Share \"{namespace}\""
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"title": "Namespace Title",
|
||||||
|
"titlePlaceholder": "The namespace title goes here…",
|
||||||
|
"description": "Description",
|
||||||
|
"descriptionPlaceholder": "The namespaces description goes here…",
|
||||||
|
"color": "Color",
|
||||||
|
"archived": "Is Archived",
|
||||||
|
"isArchived": "This namespace is archived"
|
||||||
|
},
|
||||||
|
"pseudo": {
|
||||||
|
"sharedLists": {
|
||||||
|
"title": "Shared Lists"
|
||||||
|
},
|
||||||
|
"favorites": {
|
||||||
|
"title": "Favorites"
|
||||||
|
},
|
||||||
|
"savedFilters": {
|
||||||
|
"title": "Filters"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"title": "Filters",
|
||||||
|
"clear": "Clear Filters",
|
||||||
|
"attributes": {
|
||||||
|
"title": "Title",
|
||||||
|
"titlePlaceholder": "The saved filter title goes here…",
|
||||||
|
"description": "Description",
|
||||||
|
"descriptionPlaceholder": "The description goes here…",
|
||||||
|
"includeNulls": "Include Tasks which don't have a value set",
|
||||||
|
"requireAll": "Require all filters to be true for a task to show up",
|
||||||
|
"showDoneTasks": "Show Done Tasks",
|
||||||
|
"enablePriority": "Enable Filter By Priority",
|
||||||
|
"enablePercentDone": "Enable Filter By Percent Done",
|
||||||
|
"dueDateRange": "Due Date Range",
|
||||||
|
"startDateRange": "Start Date Range",
|
||||||
|
"endDateRange": "End Date Range",
|
||||||
|
"reminderRange": "Reminder Date Range"
|
||||||
|
},
|
||||||
|
"create": {
|
||||||
|
"title": "Create A Saved Filter",
|
||||||
|
"description": "A saved filter is a virtual list which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
|
||||||
|
"action": "Create new saved filter"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"header": "Delete this saved filter",
|
||||||
|
"text": "Are you sure you want to delete this saved filter?",
|
||||||
|
"success": "The filter was deleted successfully."
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"title": "Edit This Saved Filter",
|
||||||
|
"success": "The filter was saved successfully."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"migrate": {
|
||||||
|
"title": "Migrate from other services to Vikunja",
|
||||||
|
"titleService": "Import your data from {name} into Vikunja",
|
||||||
|
"import": "Import your data into Vikunja",
|
||||||
|
"description": "Click on the logo of one of the third-party services below to get started.",
|
||||||
|
"descriptionDo": "Vikunja will import all lists, tasks, notes, reminders and files you have access to.",
|
||||||
|
"authorize": "To authorize Vikunja to access your {name} Account, click the button below.",
|
||||||
|
"getStarted": "Get Started",
|
||||||
|
"inProgress": "Importing in progress…",
|
||||||
|
"alreadyMigrated1": "It looks like you've already imported your stuff from {name} at {date}.",
|
||||||
|
"alreadyMigrated2": "Importing again is possible, but might create duplicates. Are you sure?",
|
||||||
|
"confirm": "I am sure, please start migrating now!",
|
||||||
|
"importUpload": "To import data from {name} into Vikunja, click the button below to select a file.",
|
||||||
|
"upload": "Upload file"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"title": "Labels",
|
||||||
|
"manage": "Manage labels",
|
||||||
|
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose list you have access.",
|
||||||
|
"newCTA": "You currently do not have any labels.",
|
||||||
|
"search": "Type to search for a label…",
|
||||||
|
"create": {
|
||||||
|
"header": "New label",
|
||||||
|
"title": "Create a new label",
|
||||||
|
"titleRequired": "Please specify a title.",
|
||||||
|
"success": "The label was successfully created."
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"header": "Edit Label",
|
||||||
|
"forbidden": "You are not allowed to edit this label because you dont own it.",
|
||||||
|
"success": "The label was successfully updated."
|
||||||
|
},
|
||||||
|
"deleteSuccess": "The label was successfully deleted.",
|
||||||
|
"attributes": {
|
||||||
|
"title": "Title",
|
||||||
|
"titlePlaceholder": "The label title goes here…",
|
||||||
|
"description": "Description",
|
||||||
|
"descriptionPlaceholder": "Label description",
|
||||||
|
"color": "Color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sharing": {
|
||||||
|
"authenticating": "Authenticating…",
|
||||||
|
"passwordRequired": "This shared list requires a password. Please enter it below:",
|
||||||
|
"error": "An error occured.",
|
||||||
|
"invalidPassword": "The password is invalid."
|
||||||
|
},
|
||||||
|
"navigation": {
|
||||||
|
"overview": "Overview",
|
||||||
|
"upcoming": "Upcoming",
|
||||||
|
"settings": "Settings",
|
||||||
|
"imprint": "Imprint",
|
||||||
|
"privacy": "Privacy Policy"
|
||||||
|
},
|
||||||
|
"misc": {
|
||||||
|
"loading": "Loading…",
|
||||||
|
"save": "Save",
|
||||||
|
"delete": "Delete",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"disable": "Disable",
|
||||||
|
"copy": "Copy to clipboard",
|
||||||
|
"search": "Search",
|
||||||
|
"searchPlaceholder": "Type to search…",
|
||||||
|
"previous": "Previous",
|
||||||
|
"next": "Next",
|
||||||
|
"poweredBy": "Powered by Vikunja",
|
||||||
|
"info": "Info",
|
||||||
|
"create": "Create",
|
||||||
|
"doit": "Do it!",
|
||||||
|
"saving": "Saving…",
|
||||||
|
"saved": "Saved!",
|
||||||
|
"default": "Default",
|
||||||
|
"close": "Close",
|
||||||
|
"download": "Download",
|
||||||
|
"showMenu": "Show the menu",
|
||||||
|
"hideMenu": "Hide the menu"
|
||||||
|
},
|
||||||
|
"input": {
|
||||||
|
"resetColor": "Reset Color",
|
||||||
|
"datepicker": {
|
||||||
|
"today": "Today",
|
||||||
|
"tomorrow": "Tomorrow",
|
||||||
|
"nextMonday": "Next Monday",
|
||||||
|
"thisWeekend": "This Weekend",
|
||||||
|
"laterThisWeek": "Later This Week",
|
||||||
|
"nextWeek": "Next Week",
|
||||||
|
"chooseDate": "Choose a date"
|
||||||
|
},
|
||||||
|
"editor": {
|
||||||
|
"edit": "Edit",
|
||||||
|
"done": "Done",
|
||||||
|
"heading1": "Heading 1",
|
||||||
|
"heading2": "Heading 2",
|
||||||
|
"heading3": "Heading 3",
|
||||||
|
"headingSmaller": "Heading Smaller",
|
||||||
|
"headingBigger": "Heading Bigger",
|
||||||
|
"bold": "Bold",
|
||||||
|
"italic": "Italic",
|
||||||
|
"strikethrough": "Strikethrough",
|
||||||
|
"code": "Code",
|
||||||
|
"quote": "Quote",
|
||||||
|
"unorderedList": "Unordered List",
|
||||||
|
"orderedList": "Ordered List",
|
||||||
|
"cleanBlock": "Clean Block",
|
||||||
|
"link": "Link",
|
||||||
|
"image": "Image",
|
||||||
|
"table": "Table",
|
||||||
|
"horizontalRule": "Horizontal Rule",
|
||||||
|
"sideBySide": "Side By Side",
|
||||||
|
"guide": "Guide"
|
||||||
|
},
|
||||||
|
"multiselect": {
|
||||||
|
"createPlaceholder": "Create new",
|
||||||
|
"selectPlaceholder": "Click or press enter to select"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"task": {
|
||||||
|
"task": "Task",
|
||||||
|
"new": "Create a new task",
|
||||||
|
"delete": "Delete this task",
|
||||||
|
"createSuccess": "The task was successfully created.",
|
||||||
|
"addReminder": "Add a new reminder…",
|
||||||
|
"doneSuccess": "The task was successfully marked as done.",
|
||||||
|
"undoneSuccess": "The task was successfully un-marked as done.",
|
||||||
|
"openDetail": "Open task detail view",
|
||||||
|
"checklistTotal": "{checked} of {total} tasks",
|
||||||
|
"checklistAllDone": "{total} tasks",
|
||||||
|
"show": {
|
||||||
|
"titleCurrent": "Current Tasks",
|
||||||
|
"titleDates": "Tasks from {from} until {to}",
|
||||||
|
"noDates": "Show tasks without dates",
|
||||||
|
"current": "Current tasks",
|
||||||
|
"from": "Tasks from",
|
||||||
|
"until": "until",
|
||||||
|
"today": "Today",
|
||||||
|
"nextWeek": "Next Week",
|
||||||
|
"nextMonth": "Next Month",
|
||||||
|
"noTasks": "Nothing to do — Have a nice day!"
|
||||||
|
},
|
||||||
|
"detail": {
|
||||||
|
"chooseDueDate": "Click here to set a due date",
|
||||||
|
"chooseStartDate": "Click here to set a start date",
|
||||||
|
"chooseEndDate": "Click here to set an end date",
|
||||||
|
"move": "Move task to a different list",
|
||||||
|
"done": "Done!",
|
||||||
|
"undone": "Mark as undone",
|
||||||
|
"created": "Created {0} by {1}",
|
||||||
|
"updated": "Updated {0}",
|
||||||
|
"doneAt": "Done {0}",
|
||||||
|
"updateSuccess": "The task was saved successfully.",
|
||||||
|
"deleteSuccess": "The task has been deleted successfully.",
|
||||||
|
"belongsToList": "This task belongs to list '{list}'",
|
||||||
|
"due": "Due {at}",
|
||||||
|
"closePopup": "Close popup",
|
||||||
|
"delete": {
|
||||||
|
"header": "Delete this task",
|
||||||
|
"text1": "Are you sure you want to remove this task?",
|
||||||
|
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"assign": "Assign to a user",
|
||||||
|
"label": "Add labels",
|
||||||
|
"priority": "Set Priority",
|
||||||
|
"dueDate": "Set Due Date",
|
||||||
|
"startDate": "Set a Start Date",
|
||||||
|
"endDate": "Set an End Date",
|
||||||
|
"reminders": "Set Reminders",
|
||||||
|
"repeatAfter": "Set a repeating interval",
|
||||||
|
"percentDone": "Set Percent Done",
|
||||||
|
"attachments": "Add attachments",
|
||||||
|
"relatedTasks": "Add task relations",
|
||||||
|
"moveList": "Move task",
|
||||||
|
"color": "Set task color",
|
||||||
|
"delete": "Delete task",
|
||||||
|
"favorite": "Save as favorite",
|
||||||
|
"unfavorite": "Remove from favorites"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"assignees": "Assignees",
|
||||||
|
"color": "Color",
|
||||||
|
"created": "Created",
|
||||||
|
"createdBy": "Created By",
|
||||||
|
"description": "Description",
|
||||||
|
"done": "Done",
|
||||||
|
"dueDate": "Due Date",
|
||||||
|
"endDate": "End Date",
|
||||||
|
"labels": "Labels",
|
||||||
|
"percentDone": "% Done",
|
||||||
|
"priority": "Priority",
|
||||||
|
"relatedTasks": "Related Tasks",
|
||||||
|
"reminders": "Reminders",
|
||||||
|
"repeat": "Repeat",
|
||||||
|
"startDate": "Start Date",
|
||||||
|
"title": "Title",
|
||||||
|
"updated": "Updated"
|
||||||
|
},
|
||||||
|
"subscription": {
|
||||||
|
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
|
||||||
|
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
|
||||||
|
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
|
||||||
|
"subscribe": "Subscribe",
|
||||||
|
"unsubscribe": "Unsubscribe",
|
||||||
|
"subscribeSuccess": "You are now subscribed to this {entity}",
|
||||||
|
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
|
||||||
|
},
|
||||||
|
"attachment": {
|
||||||
|
"title": "Attachments",
|
||||||
|
"createdBy": "created {0} by {1}",
|
||||||
|
"downloadTooltip": "Download this attachment",
|
||||||
|
"upload": "Upload attachment",
|
||||||
|
"drop": "Drop files here to upload",
|
||||||
|
"delete": "Delete attachment",
|
||||||
|
"deleteTooltip": "Delete this attachment",
|
||||||
|
"deleteText1": "Are you sure you want to delete the attachment {filename}?",
|
||||||
|
"deleteText2": "This cannot be undone!",
|
||||||
|
"copyUrl": "Copy URL",
|
||||||
|
"copyUrlTooltip": "Copy the url of this attachment for usage in text"
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"title": "Comments",
|
||||||
|
"loading": "Loading comments…",
|
||||||
|
"edited": "edited {date}",
|
||||||
|
"creating": "Creating comment…",
|
||||||
|
"placeholder": "Add your comment…",
|
||||||
|
"comment": "Comment",
|
||||||
|
"delete": "Delete this comment",
|
||||||
|
"deleteText1": "Are you sure you want to delete this comment?",
|
||||||
|
"deleteText2": "This cannot be undone!",
|
||||||
|
"addedSuccess": "The comment was added successfully."
|
||||||
|
},
|
||||||
|
"deferDueDate": {
|
||||||
|
"title": "Defer due date",
|
||||||
|
"1day": "1 day",
|
||||||
|
"3days": "3 days",
|
||||||
|
"1week": "1 week"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"placeholder": "Click here to enter a description…",
|
||||||
|
"empty": "No description available yet."
|
||||||
|
},
|
||||||
|
"assignee": {
|
||||||
|
"placeholder": "Type to assign a user…",
|
||||||
|
"selectPlaceholder": "Assign this user",
|
||||||
|
"assignSuccess": "The user has been assigned successfully.",
|
||||||
|
"unassignSuccess": "The user has been unassigned successfully."
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"placeholder": "Type to add a new label…",
|
||||||
|
"createPlaceholder": "Add this as new label",
|
||||||
|
"addSuccess": "The label has been added successfully.",
|
||||||
|
"createSuccess": "The label has been created successfully.",
|
||||||
|
"removeSuccess": "The label has been removed successfully.",
|
||||||
|
"addCreateSuccess": "The label has been created and added successfully."
|
||||||
|
},
|
||||||
|
"priority": {
|
||||||
|
"unset": "Unset",
|
||||||
|
"low": "Low",
|
||||||
|
"medium": "Medium",
|
||||||
|
"high": "high",
|
||||||
|
"urgent": "Urgent",
|
||||||
|
"doNow": "DO NOW"
|
||||||
|
},
|
||||||
|
"relation": {
|
||||||
|
"add": "Add a New Task Relation",
|
||||||
|
"new": "New Task Relation",
|
||||||
|
"searchPlaceholder": "Type search for a new task to add as related…",
|
||||||
|
"createPlaceholder": "Add this as new related task",
|
||||||
|
"differentList": "This task belongs to a different list.",
|
||||||
|
"differentNamespace": "This task belongs to a different namespace.",
|
||||||
|
"noneYet": "No task relations yet.",
|
||||||
|
"delete": "Delete Task Relation",
|
||||||
|
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||||
|
"deleteText2": "This cannot be undone!",
|
||||||
|
"select": "Select a relation kind",
|
||||||
|
"kinds": {
|
||||||
|
"subtask": "Subtask | Subtasks",
|
||||||
|
"parenttask": "Parent Task | Parent Tasks",
|
||||||
|
"related": "Related Task | Related Tasks",
|
||||||
|
"duplicateof": "Duplicate Of | Duplicates Of",
|
||||||
|
"duplicates": "Duplicates | Duplicates",
|
||||||
|
"blocking": "Blocking | Blocking",
|
||||||
|
"blocked": "Blocked By | Blocked By",
|
||||||
|
"precedes": "Precedes | Precedes",
|
||||||
|
"follows": "Follows | Follows",
|
||||||
|
"copiedfrom": "Copied From | Copied From",
|
||||||
|
"copiedto": "Copied To | Copied To"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeat": {
|
||||||
|
"everyDay": "Every Day",
|
||||||
|
"everyWeek": "Every Week",
|
||||||
|
"everyMonth": "Every Month",
|
||||||
|
"mode": "Repeat mode",
|
||||||
|
"monthly": "Monthly",
|
||||||
|
"fromCurrentDate": "From Current Date",
|
||||||
|
"each": "Each",
|
||||||
|
"specifyAmount": "Specify an amount…",
|
||||||
|
"hours": "Hours",
|
||||||
|
"days": "Days",
|
||||||
|
"weeks": "Weeks",
|
||||||
|
"months": "Months",
|
||||||
|
"years": "Years"
|
||||||
|
},
|
||||||
|
"quickAddMagic": {
|
||||||
|
"hint": "You can use Quick Add Magic",
|
||||||
|
"what": "What?",
|
||||||
|
"title": "Quick Add Magic",
|
||||||
|
"intro": "When creating a task, you can use special keywords to directly add attributes to the newly created task. This allows to add commonly used attributes to tasks much faster.",
|
||||||
|
"multiple": "You can use this multiple times.",
|
||||||
|
"label1": "To add a label, simply prefix the name of the label with {prefix}.",
|
||||||
|
"label2": "Vikunja will first check if the label already exist and create it if not.",
|
||||||
|
"label3": "To use spaces, simply add a \" around the label name.",
|
||||||
|
"label4": "For example: {prefix}\"Label with spaces\".",
|
||||||
|
"priority1": "To set a task's priority, add a number 1-5, prefixed with a {prefix}.",
|
||||||
|
"priority2": "The higher the number, the higher the priority.",
|
||||||
|
"assignees": "To directly assign the task to a user, add their username prefixed with {prefix} to the task.",
|
||||||
|
"list1": "To set a list for the task to appear in, enter its name prefixed with {prefix}.",
|
||||||
|
"list2": "This will return an error if the list does not exist.",
|
||||||
|
"dateAndTime": "Date and time",
|
||||||
|
"date": "Any date will be used as the due date of the new task. You can use dates in any of these formats:",
|
||||||
|
"dateWeekday": "any weekday, will use the next date with that date",
|
||||||
|
"dateCurrentYear": "will use the current year",
|
||||||
|
"dateNth": "will use the {day}th of the current month",
|
||||||
|
"dateTime": "Combine any of the date formats with \"{time}\" (or {timePM}) to set a time."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"team": {
|
||||||
|
"title": "Teams",
|
||||||
|
"noTeams": "You are currently not part of any teams.",
|
||||||
|
"create": {
|
||||||
|
"title": "Create a new team",
|
||||||
|
"success": "The team was successfully created."
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"title": "Edit Team \"{team}\"",
|
||||||
|
"members": "Team Members",
|
||||||
|
"search": "Type to search a user…",
|
||||||
|
"addUser": "Add to team",
|
||||||
|
"makeMember": "Make Member",
|
||||||
|
"makeAdmin": "Make Admin",
|
||||||
|
"success": "The team was successfully updated.",
|
||||||
|
"userAddedSuccess": "The team member was successfully added.",
|
||||||
|
"madeMember": "The team member was successfully made member.",
|
||||||
|
"madeAdmin": "The team member was successfully made admin.",
|
||||||
|
"delete": {
|
||||||
|
"header": "Delete the team",
|
||||||
|
"text1": "Are you sure you want to delete this team and all of its members?",
|
||||||
|
"text2": "All team members will lose access to lists and namespaces shared with this team. This CANNOT BE UNDONE!",
|
||||||
|
"success": "The team was successfully deleted."
|
||||||
|
},
|
||||||
|
"deleteUser": {
|
||||||
|
"header": "Remove a user from the team",
|
||||||
|
"text1": "Are you sure you want to remove this user from the team?",
|
||||||
|
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
|
||||||
|
"success": "The user was successfully deleted from the team."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"name": "Team Name",
|
||||||
|
"namePlaceholder": "The team's name goes here…",
|
||||||
|
"nameRequired": "Please specify a name.",
|
||||||
|
"description": "Description",
|
||||||
|
"descriptionPlaceholder": "The teams description goes here…",
|
||||||
|
"admin": "Admin",
|
||||||
|
"member": "Member"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keyboardShortcuts": {
|
||||||
|
"title": "Keyboard Shortcuts",
|
||||||
|
"general": "General",
|
||||||
|
"allPages": "These shortcuts work on all pages.",
|
||||||
|
"currentPageOnly": "These shortcuts work only on the current page.",
|
||||||
|
"toggleMenu": "Toggle The Menu",
|
||||||
|
"quickSearch": "Open the search/quick action bar",
|
||||||
|
"then": "then",
|
||||||
|
"task": {
|
||||||
|
"title": "Task Page",
|
||||||
|
"done": "Mark a task as done",
|
||||||
|
"assign": "Assign to a user",
|
||||||
|
"labels": "Add labels to this task",
|
||||||
|
"dueDate": "Change the due date of this task",
|
||||||
|
"attachment": "Add an attachment to this task",
|
||||||
|
"related": "Modify related tasks of this task"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "List Views",
|
||||||
|
"switchToListView": "Switch to list view",
|
||||||
|
"switchToGanttView": "Switch to gantt view",
|
||||||
|
"switchToKanbanView": "Switch to kanban view",
|
||||||
|
"switchToTableView": "Switch to table view"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"available": "There is an update for Vikunja available!",
|
||||||
|
"do": "Update Now"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"edit": "Edit",
|
||||||
|
"archive": "Archive",
|
||||||
|
"duplicate": "Duplicate",
|
||||||
|
"delete": "Delete",
|
||||||
|
"unarchive": "Un-Archive",
|
||||||
|
"setBackground": "Set background",
|
||||||
|
"share": "Share",
|
||||||
|
"newList": "New list"
|
||||||
|
},
|
||||||
|
"apiConfig": {
|
||||||
|
"url": "Vikunja URL",
|
||||||
|
"urlPlaceholder": "eg. https://localhost:3456",
|
||||||
|
"change": "change",
|
||||||
|
"signInOn": "Sign in to your Vikunja account on {0}",
|
||||||
|
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.",
|
||||||
|
"success": "Using Vikunja installation at \"{domain}\".",
|
||||||
|
"urlRequired": "A url is required."
|
||||||
|
},
|
||||||
|
"loadingError": {
|
||||||
|
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||||
|
"tryAgain": "try again",
|
||||||
|
"contact": "contact us"
|
||||||
|
},
|
||||||
|
"notification": {
|
||||||
|
"title": "Notifications",
|
||||||
|
"none": "You don't have any notifications. Have a nice day!",
|
||||||
|
"explainer": "Notifications will appear here when actions on namespaces, lists or tasks you subscribed to happen."
|
||||||
|
},
|
||||||
|
"quickActions": {
|
||||||
|
"commands": "Commands",
|
||||||
|
"placeholder": "Type a command or search…",
|
||||||
|
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
|
||||||
|
"tasks": "Tasks",
|
||||||
|
"lists": "Lists",
|
||||||
|
"teams": "Teams",
|
||||||
|
"newList": "Enter the title of the new list…",
|
||||||
|
"newTask": "Enter the title of the new task…",
|
||||||
|
"newNamespace": "Enter the title of the new namespace…",
|
||||||
|
"newTeam": "Enter the name of the new team…",
|
||||||
|
"createTask": "Create a task in the current list ({title})",
|
||||||
|
"createList": "Create a list in the current namespace ({title})",
|
||||||
|
"cmds": {
|
||||||
|
"newTask": "New task",
|
||||||
|
"newList": "New list",
|
||||||
|
"newNamespace": "New namespace",
|
||||||
|
"newTeam": "New team"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"locale": "en",
|
||||||
|
"altFormatLong": "j M Y H:i",
|
||||||
|
"altFormatShort": "j M Y"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"error": "Error",
|
||||||
|
"success": "Success",
|
||||||
|
"0001": "You're not allowed to do that.",
|
||||||
|
"1001": "A user with this username already exists.",
|
||||||
|
"1002": "A user with this email address already exists.",
|
||||||
|
"1004": "No username and password specified.",
|
||||||
|
"1005": "The user does not exist.",
|
||||||
|
"1006": "Could not get the user id.",
|
||||||
|
"1008": "No password reset token provided.",
|
||||||
|
"1009": "Invalid password reset token.",
|
||||||
|
"1010": "Invalid email confirm token.",
|
||||||
|
"1011": "Wrong username or password.",
|
||||||
|
"1012": "Email address of the user not confirmed.",
|
||||||
|
"1013": "New password is empty.",
|
||||||
|
"1014": "Old password is empty.",
|
||||||
|
"1015": "Totp is already enabled for this user.",
|
||||||
|
"1016": "Totp is not enabled for this user.",
|
||||||
|
"1017": "The totp passcode is invalid.",
|
||||||
|
"1018": "The user avatar type setting is invalid.",
|
||||||
|
"2001": "ID cannot be empty or 0.",
|
||||||
|
"2002": "Some of the request data was invalid.",
|
||||||
|
"3001": "The list does not exist.",
|
||||||
|
"3004": "You need to have read permissions on that list to perform that action.",
|
||||||
|
"3005": "The list title cannot be empty.",
|
||||||
|
"3006": "The list share does not exist.",
|
||||||
|
"3007": "A list with this identifier already exists.",
|
||||||
|
"3008": "The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list.",
|
||||||
|
"4001": "The list task text cannot be empty.",
|
||||||
|
"4002": "The list task does not exist.",
|
||||||
|
"4003": "All bulk editing tasks must belong to the same list.",
|
||||||
|
"4004": "Need at least one task when bulk editing tasks.",
|
||||||
|
"4005": "You do not have the right to see the task.",
|
||||||
|
"4006": "You can't set a parent task as the task itself.",
|
||||||
|
"4007": "You can't create a task relation with an invalid kind of relation.",
|
||||||
|
"4008": "You can't create a task relation which already exists.",
|
||||||
|
"4009": "The task relation does not exist.",
|
||||||
|
"4010": "Cannot relate a task with itself.",
|
||||||
|
"4011": "The task attachment does not exist.",
|
||||||
|
"4012": "The task attachment is too large.",
|
||||||
|
"4013": "The task sort param is invalid.",
|
||||||
|
"4014": "The task sort order is invalid.",
|
||||||
|
"4015": "The task comment does not exist.",
|
||||||
|
"4016": "Invalid task field.",
|
||||||
|
"4017": "Invalid task filter comparator.",
|
||||||
|
"4018": "Invalid task filter concatinator.",
|
||||||
|
"4019": "Invalid task filter value.",
|
||||||
|
"5001": "The namespace does not exist.",
|
||||||
|
"5003": "You do not have access to the specified namespace.",
|
||||||
|
"5006": "The namespace name cannot be empty.",
|
||||||
|
"5009": "You need to have namespace read access to perform that action.",
|
||||||
|
"5010": "This team does not have access to that namespace.",
|
||||||
|
"5011": "This user has already access to that namespace.",
|
||||||
|
"5012": "The namespace is archived and can therefore only be accessed read only.",
|
||||||
|
"6001": "The team name cannot be emtpy.",
|
||||||
|
"6002": "The team does not exist.",
|
||||||
|
"6004": "The team already has access to that namespace or list.",
|
||||||
|
"6005": "The user is already a member of that team.",
|
||||||
|
"6006": "Cannot delete the last team member.",
|
||||||
|
"6007": "The team does not have access to the list to perform that action.",
|
||||||
|
"7002": "The user already has access to that list.",
|
||||||
|
"7003": "You do not have access to that list.",
|
||||||
|
"8001": "This label already exists on that task.",
|
||||||
|
"8002": "The label does not exist.",
|
||||||
|
"8003": "You do not have access to this label.",
|
||||||
|
"9001": "The right is invalid.",
|
||||||
|
"10001": "The bucket does not exist.",
|
||||||
|
"10002": "The bucket does not belong to that list.",
|
||||||
|
"10003": "You cannot remove the last bucket on a list.",
|
||||||
|
"10004": "You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold.",
|
||||||
|
"10005": "There can be only one done bucket per list.",
|
||||||
|
"11001": "The saved filter does not exist.",
|
||||||
|
"11002": "Saved filters are not available for link shares.",
|
||||||
|
"12001": "The subscription entity type is invalid.",
|
||||||
|
"12002": "You are already subscribed to the entity itself or a parent entity.",
|
||||||
|
"13001": "This link share requires a password for authentication, but none was provided.",
|
||||||
|
"13002": "The provided link share password was invalid."
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"title": "About",
|
||||||
|
"frontendVersion": "Frontend Version: {version}",
|
||||||
|
"apiVersion": "API Version: {version}"
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,16 @@
|
||||||
"title": "Không tìm thấy gì cả",
|
"title": "Không tìm thấy gì cả",
|
||||||
"text": "Trang bạn yêu cầu không tồn tại."
|
"text": "Trang bạn yêu cầu không tồn tại."
|
||||||
},
|
},
|
||||||
|
"ready": {
|
||||||
|
"loading": "Vikunja đang tải…",
|
||||||
|
"errorOccured": "Đã xảy ra lỗi:",
|
||||||
|
"checkApiUrl": "Vui lòng kiểm tra lại url api.",
|
||||||
|
"noApiUrlConfigured": "Không có url API nào được cấu hình. Hãy đặt một cái:"
|
||||||
|
},
|
||||||
|
"offline": {
|
||||||
|
"title": "Bạn đang offline.",
|
||||||
|
"text": "Vui lòng kiểm tra kết nối mạng của bạn và thử lại."
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"username": "Tên người dùng",
|
"username": "Tên người dùng",
|
||||||
|
@ -102,6 +112,15 @@
|
||||||
"disabled": "Vô hiệu hóa",
|
"disabled": "Vô hiệu hóa",
|
||||||
"todoist": "Todoist",
|
"todoist": "Todoist",
|
||||||
"vikunja": "Vikunja"
|
"vikunja": "Vikunja"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"title": "Color Scheme",
|
||||||
|
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||||
|
"colorScheme": {
|
||||||
|
"light": "Light",
|
||||||
|
"system": "System",
|
||||||
|
"dark": "Dark"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
|
@ -344,6 +363,7 @@
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"title": "Bộ lọc",
|
"title": "Bộ lọc",
|
||||||
|
"clear": "Xoá các bộ lọc",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"title": "Tiêu đề",
|
"title": "Tiêu đề",
|
||||||
"titlePlaceholder": "Tiêu đề bộ lọc đã lưu ở đây…",
|
"titlePlaceholder": "Tiêu đề bộ lọc đã lưu ở đây…",
|
||||||
|
@ -449,7 +469,9 @@
|
||||||
"saved": "Đã lưu!",
|
"saved": "Đã lưu!",
|
||||||
"default": "Mặc định",
|
"default": "Mặc định",
|
||||||
"close": "Đóng",
|
"close": "Đóng",
|
||||||
"download": "Tải về"
|
"download": "Tải về",
|
||||||
|
"showMenu": "Hiển thị menu",
|
||||||
|
"hideMenu": "Ẩn menu"
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"resetColor": "Đặt lại màu",
|
"resetColor": "Đặt lại màu",
|
||||||
|
@ -534,7 +556,7 @@
|
||||||
"text2": "Thao tác này cũng sẽ xóa tất cả tệp đính kèm, lời nhắc và liên kết đến công việc này. Nó không thể hoàn tác!"
|
"text2": "Thao tác này cũng sẽ xóa tất cả tệp đính kèm, lời nhắc và liên kết đến công việc này. Nó không thể hoàn tác!"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"assign": "Chỉ định người đảm nhiệm",
|
"assign": "Assign to a user",
|
||||||
"label": "Thêm nhãn",
|
"label": "Thêm nhãn",
|
||||||
"priority": "Mức độ ưu tiên",
|
"priority": "Mức độ ưu tiên",
|
||||||
"dueDate": "Đặt ngày đến hạn",
|
"dueDate": "Đặt ngày đến hạn",
|
||||||
|
@ -643,6 +665,7 @@
|
||||||
"searchPlaceholder": "Gõ tìm kiếm một công việc để thêm dưới dạng liên quan…",
|
"searchPlaceholder": "Gõ tìm kiếm một công việc để thêm dưới dạng liên quan…",
|
||||||
"createPlaceholder": "Thêm điều này làm công việc liên quan mới",
|
"createPlaceholder": "Thêm điều này làm công việc liên quan mới",
|
||||||
"differentList": "Công việc này thuộc về một danh sách khác.",
|
"differentList": "Công việc này thuộc về một danh sách khác.",
|
||||||
|
"differentNamespace": "Công việc này thuộc về một Góc làm việc khác.",
|
||||||
"noneYet": "Không có công việc liên quan nào.",
|
"noneYet": "Không có công việc liên quan nào.",
|
||||||
"delete": "Xóa công việc liên quan",
|
"delete": "Xóa công việc liên quan",
|
||||||
"deleteText1": "Bạn có chắc muốn xóa công việc liên quan này không?",
|
"deleteText1": "Bạn có chắc muốn xóa công việc liên quan này không?",
|
||||||
|
@ -743,18 +766,27 @@
|
||||||
},
|
},
|
||||||
"keyboardShortcuts": {
|
"keyboardShortcuts": {
|
||||||
"title": "Phím tắt",
|
"title": "Phím tắt",
|
||||||
|
"general": "Tổng quan",
|
||||||
"allPages": "Các phím tắt này hoạt động ở mọi nơi.",
|
"allPages": "Các phím tắt này hoạt động ở mọi nơi.",
|
||||||
"currentPageOnly": "Các phím tắt này chỉ hoạt động ở trang hiện tại.",
|
"currentPageOnly": "Các phím tắt này chỉ hoạt động ở trang hiện tại.",
|
||||||
"toggleMenu": "Bật/tắt Menu",
|
"toggleMenu": "Bật/tắt Menu",
|
||||||
"quickSearch": "Mở thanh tìm kiếm/thao tác nhanh",
|
"quickSearch": "Mở thanh tìm kiếm/thao tác nhanh",
|
||||||
|
"then": "sau đó",
|
||||||
"task": {
|
"task": {
|
||||||
"title": "Trang công việc",
|
"title": "Trang công việc",
|
||||||
"done": "Đánh dấu hoàn thành",
|
"done": "Đánh dấu hoàn thành",
|
||||||
"assign": "Chỉnh định việc này cho một người",
|
"assign": "Assign to a user",
|
||||||
"labels": "Thêm nhãn cho công việc này",
|
"labels": "Thêm nhãn cho công việc này",
|
||||||
"dueDate": "Thay đổi ngày hết hạn của công việc này",
|
"dueDate": "Thay đổi ngày hết hạn của công việc này",
|
||||||
"attachment": "Thêm tệp đính kèm cho công việc này",
|
"attachment": "Thêm tệp đính kèm cho công việc này",
|
||||||
"related": "Sửa đổi các công việc liên kết"
|
"related": "Sửa đổi các công việc liên kết"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"title": "Xem danh sách",
|
||||||
|
"switchToListView": "Chuyển sang chế độ danh sách",
|
||||||
|
"switchToGanttView": "Chuyển sang biểu đồ Gantt",
|
||||||
|
"switchToKanbanView": "Chuyển sang Kanban",
|
||||||
|
"switchToTableView": "Chuyển qua xem Bảng"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
|
@ -776,8 +808,9 @@
|
||||||
"urlPlaceholder": "ví dụ: https://localhost:3456",
|
"urlPlaceholder": "ví dụ: https://localhost:3456",
|
||||||
"change": "thay đổi",
|
"change": "thay đổi",
|
||||||
"signInOn": "Đăng nhập vào tài khoản Vikunia tại {0}",
|
"signInOn": "Đăng nhập vào tài khoản Vikunia tại {0}",
|
||||||
"error": "Không thể tìm kiếm hay sử dụng cài đặt Vukunja tại \"{domain}\".",
|
"error": "Không thể tìm thấy hoặc sử dụng cài đặt Vikunja tại \"{domain}\". Vui lòng thử một url khác.",
|
||||||
"success": "Sử dụng cài đặt Vikunja tại \"{domain}\"."
|
"success": "Sử dụng cài đặt Vikunja tại \"{domain}\".",
|
||||||
|
"urlRequired": "Cần có một url."
|
||||||
},
|
},
|
||||||
"loadingError": {
|
"loadingError": {
|
||||||
"failed": "Tải thất bại, vui lòng {0}. Nếu lỗi vẫn xảy ra, vui lòng {1}.",
|
"failed": "Tải thất bại, vui lòng {0}. Nếu lỗi vẫn xảy ra, vui lòng {1}.",
|
||||||
|
@ -792,7 +825,7 @@
|
||||||
"quickActions": {
|
"quickActions": {
|
||||||
"commands": "Các lệnh",
|
"commands": "Các lệnh",
|
||||||
"placeholder": "Gõ một lệnh hoặc tìm kiếm…",
|
"placeholder": "Gõ một lệnh hoặc tìm kiếm…",
|
||||||
"hint": "Bạn có thể dùng # để chỉ tìm kiếm công việc, * để chỉ tìm kiếm danh sách và @ để chỉ tìm kiếm Team.",
|
"hint": "Bạn có thể dùng {list} để giới hạn tìm kiếm trong một danh sách. Kết hợp {list} hoặc {label} (các nhãn) với một truy vấn để tìm kiếm một công việc được gắn các nhãn này hoặc trên danh sách đó. Sử dụng {assignee} để chỉ tìm kiếm các Team.",
|
||||||
"tasks": "Tác vụ",
|
"tasks": "Tác vụ",
|
||||||
"lists": "Danh sách",
|
"lists": "Danh sách",
|
||||||
"teams": "Team",
|
"teams": "Team",
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import {Document, SimpleDocumentSearchResultSetUnit} from 'flexsearch'
|
||||||
|
|
||||||
|
export interface withId {
|
||||||
|
id: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexes: { [k: string]: Document<withId> } = {}
|
||||||
|
|
||||||
|
export const createNewIndexer = (name: string, fieldsToIndex: string[]) => {
|
||||||
|
if (typeof indexes[name] === 'undefined') {
|
||||||
|
indexes[name] = new Document<withId>({
|
||||||
|
tokenize: 'full',
|
||||||
|
document: {
|
||||||
|
id: 'id',
|
||||||
|
index: fieldsToIndex,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = indexes[name]
|
||||||
|
|
||||||
|
function add(item: withId) {
|
||||||
|
return index.add(item.id, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(item: withId) {
|
||||||
|
return index.remove(item.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(item: withId) {
|
||||||
|
return index.update(item.id, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
function search(query: string | null): number[] | null {
|
||||||
|
if (query === '' || query === null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return index.search(query)
|
||||||
|
?.flatMap(r => r.result)
|
||||||
|
.filter((value, index, self) => self.indexOf(value) === index)
|
||||||
|
|| null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
add,
|
||||||
|
remove,
|
||||||
|
update,
|
||||||
|
search,
|
||||||
|
}
|
||||||
|
}
|
23
src/main.ts
23
src/main.ts
|
@ -17,7 +17,7 @@ declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
API_URL: string;
|
API_URL: string;
|
||||||
SENTRY_ENABLED: boolean;
|
SENTRY_ENABLED: boolean;
|
||||||
SENTRY_DSN: string,
|
SENTRY_DSN: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,8 +31,6 @@ import Notifications from '@kyvg/vue3-notification'
|
||||||
// PWA
|
// PWA
|
||||||
import './registerServiceWorker'
|
import './registerServiceWorker'
|
||||||
|
|
||||||
// Shortcuts
|
|
||||||
import shortkey from '@/plugins/shortkey'
|
|
||||||
// Vuex
|
// Vuex
|
||||||
import {store} from './store'
|
import {store} from './store'
|
||||||
// i18n
|
// i18n
|
||||||
|
@ -55,20 +53,23 @@ const app = createApp(App)
|
||||||
|
|
||||||
app.use(Notifications)
|
app.use(Notifications)
|
||||||
|
|
||||||
app.use(shortkey, {prevent: ['input', 'textarea', '.input', '[contenteditable]']})
|
|
||||||
|
|
||||||
// directives
|
// directives
|
||||||
import focus from './directives/focus'
|
import focus from '@/directives/focus'
|
||||||
import tooltip from './directives/tooltip'
|
import { VTooltip } from 'v-tooltip'
|
||||||
|
import 'v-tooltip/dist/v-tooltip.css'
|
||||||
|
import shortcut from '@/directives/shortcut'
|
||||||
|
import cypress from '@/directives/cypress'
|
||||||
|
|
||||||
app.directive('focus', focus)
|
app.directive('focus', focus)
|
||||||
app.directive('tooltip', tooltip)
|
app.directive('tooltip', VTooltip)
|
||||||
|
app.directive('shortcut', shortcut)
|
||||||
|
app.directive('cy', cypress)
|
||||||
|
|
||||||
// global components
|
// global components
|
||||||
import FontAwesomeIcon from './icons'
|
import FontAwesomeIcon from './icons'
|
||||||
import Button from './components/input/button.vue'
|
import Button from '@/components/input/button.vue'
|
||||||
import Modal from './components/modal/modal.vue'
|
import Modal from '@/components/modal/modal.vue'
|
||||||
import Card from './components/misc/card.vue'
|
import Card from '@/components/misc/card.vue'
|
||||||
|
|
||||||
app.component('icon', FontAwesomeIcon)
|
app.component('icon', FontAwesomeIcon)
|
||||||
app.component('x-button', Button)
|
app.component('x-button', Button)
|
||||||
|
|
|
@ -7,6 +7,8 @@ import {REPEAT_MODE_DEFAULT} from './constants/taskRepeatModes'
|
||||||
import SubscriptionModel from '@/models/subscription'
|
import SubscriptionModel from '@/models/subscription'
|
||||||
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
||||||
|
|
||||||
|
const SUPPORTS_TRIGGERED_NOTIFICATION = 'Notification' in window && 'showTrigger' in Notification.prototype
|
||||||
|
|
||||||
export default class TaskModel extends AbstractModel {
|
export default class TaskModel extends AbstractModel {
|
||||||
|
|
||||||
defaultColor = '198CFF'
|
defaultColor = '198CFF'
|
||||||
|
@ -161,7 +163,7 @@ export default class TaskModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancelScheduledNotifications() {
|
async cancelScheduledNotifications() {
|
||||||
if (!(Notification && 'showTrigger' in Notification.prototype)) {
|
if (!SUPPORTS_TRIGGERED_NOTIFICATION) {
|
||||||
console.debug('This browser does not support triggered notifications')
|
console.debug('This browser does not support triggered notifications')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -196,7 +198,7 @@ export default class TaskModel extends AbstractModel {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(Notification && 'showTrigger' in Notification.prototype)) {
|
if (!SUPPORTS_TRIGGERED_NOTIFICATION) {
|
||||||
console.debug('This browser does not support triggered notifications')
|
console.debug('This browser does not support triggered notifications')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
function capitalizeFirstLetter(string) {
|
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const MODIFIER_KEYS = ['shift', 'ctrl', 'meta', 'alt']
|
|
||||||
|
|
||||||
const SHORT_CUT_INDEX = [
|
|
||||||
{ key: 'ArrowUp', value: 'arrowup' },
|
|
||||||
{ key: 'ArrowLeft', value: 'arrowlef' },
|
|
||||||
{ key: 'ArrowRight', value: 'arrowright' },
|
|
||||||
{ key: 'ArrowDown', value: 'arrowdown' },
|
|
||||||
{ key: 'AltGraph', value: 'altgraph' },
|
|
||||||
{ key: 'Escape', value: 'esc' },
|
|
||||||
{ key: 'Enter', value: 'enter' },
|
|
||||||
{ key: 'Tab', value: 'tab' },
|
|
||||||
{ key: ' ', value: 'space' },
|
|
||||||
{ key: 'PageUp', value: 'pagup' },
|
|
||||||
{ key: 'PageDown', value: 'pagedow' },
|
|
||||||
{ key: 'Home', value: 'home' },
|
|
||||||
{ key: 'End', value: 'end' },
|
|
||||||
{ key: 'Delete', value: 'del' },
|
|
||||||
{ key: 'Backspace', value: 'bacspace' },
|
|
||||||
{ key: 'Insert', value: 'insert' },
|
|
||||||
{ key: 'NumLock', value: 'numlock' },
|
|
||||||
{ key: 'CapsLock', value: 'capslock' },
|
|
||||||
{ key: 'Pause', value: 'pause' },
|
|
||||||
{ key: 'ContextMenu', value: 'cotextmenu' },
|
|
||||||
{ key: 'ScrollLock', value: 'scrolllock' },
|
|
||||||
{ key: 'BrowserHome', value: 'browserhome' },
|
|
||||||
{ key: 'MediaSelect', value: 'mediaselect' },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function encodeKey(pKey) {
|
|
||||||
const shortKey = {}
|
|
||||||
|
|
||||||
MODIFIER_KEYS.forEach((key) => {
|
|
||||||
shortKey[`${key}Key`] = pKey.includes(key)
|
|
||||||
})
|
|
||||||
|
|
||||||
let indexedKeys = createShortcutIndex(shortKey)
|
|
||||||
const vKey = pKey.filter(
|
|
||||||
(item) => !MODIFIER_KEYS.includes(item),
|
|
||||||
)
|
|
||||||
indexedKeys += vKey.join('')
|
|
||||||
return indexedKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
function createShortcutIndex(pKey) {
|
|
||||||
let k = ''
|
|
||||||
|
|
||||||
MODIFIER_KEYS.forEach((key) => {
|
|
||||||
if (pKey.key === capitalizeFirstLetter(key) || pKey[`${key}Key`]) {
|
|
||||||
k += key
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
SHORT_CUT_INDEX.forEach(({ key, value }) => {
|
|
||||||
if (pKey.key === key) {
|
|
||||||
k += value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (
|
|
||||||
(pKey.key && pKey.key !== ' ' && pKey.key.length === 1) ||
|
|
||||||
/F\d{1,2}|\//g.test(pKey.key)
|
|
||||||
) {
|
|
||||||
k += pKey.key.toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
export { createShortcutIndex as decodeKey }
|
|
||||||
|
|
||||||
export function parseValue(value) {
|
|
||||||
value = typeof value === 'string' ? JSON.parse(value.replace(/'/gi, '"')) : value
|
|
||||||
|
|
||||||
return value instanceof Array ? { '': value } : value
|
|
||||||
}
|
|
|
@ -1,186 +0,0 @@
|
||||||
import { parseValue, decodeKey, encodeKey } from './helpers'
|
|
||||||
|
|
||||||
let mapFunctions = {}
|
|
||||||
let objAvoided = []
|
|
||||||
let elementAvoided = []
|
|
||||||
let keyPressed = false
|
|
||||||
|
|
||||||
function dispatchShortkeyEvent(pKey) {
|
|
||||||
const e = new CustomEvent('shortkey', { bubbles: false })
|
|
||||||
|
|
||||||
if (mapFunctions[pKey].key) {
|
|
||||||
e.srcKey = mapFunctions[pKey].key
|
|
||||||
}
|
|
||||||
|
|
||||||
const elm = mapFunctions[pKey].el
|
|
||||||
|
|
||||||
if (!mapFunctions[pKey].propagte) {
|
|
||||||
elm[elm.length - 1].dispatchEvent(e)
|
|
||||||
} else {
|
|
||||||
elm.forEach((elmItem) => elmItem.dispatchEvent(e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function keyDown(pKey) {
|
|
||||||
if (
|
|
||||||
(!mapFunctions[pKey].once && !mapFunctions[pKey].push) ||
|
|
||||||
(mapFunctions[pKey].push && !keyPressed)
|
|
||||||
) {
|
|
||||||
dispatchShortkeyEvent(pKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function fillMappingFunctions(
|
|
||||||
mappingFunctions,
|
|
||||||
{ b, push, once, focus, propagte, el },
|
|
||||||
) {
|
|
||||||
for (let key in b) {
|
|
||||||
const k = encodeKey(b[key])
|
|
||||||
const propagated = mappingFunctions[k] && mappingFunctions[k].propagte
|
|
||||||
const elm =
|
|
||||||
mappingFunctions[k] && mappingFunctions[k].el
|
|
||||||
? mappingFunctions[k].el
|
|
||||||
: []
|
|
||||||
|
|
||||||
elm.push(el)
|
|
||||||
|
|
||||||
mappingFunctions[k] = {
|
|
||||||
push,
|
|
||||||
once,
|
|
||||||
focus,
|
|
||||||
key,
|
|
||||||
propagte: propagated || propagte,
|
|
||||||
el: elm,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function bindValue(value, el, binding, vnode) {
|
|
||||||
const { modifiers } = binding
|
|
||||||
const push = !!modifiers.push
|
|
||||||
const avoid = !!modifiers.avoid
|
|
||||||
const focus = !modifiers.focus
|
|
||||||
const once = !!modifiers.once
|
|
||||||
const propagte = !!modifiers.propagte
|
|
||||||
|
|
||||||
if (avoid) {
|
|
||||||
objAvoided = objAvoided.filter((itm) => !itm === el)
|
|
||||||
objAvoided.push(el)
|
|
||||||
} else {
|
|
||||||
fillMappingFunctions(mapFunctions, {
|
|
||||||
b: value,
|
|
||||||
push,
|
|
||||||
once,
|
|
||||||
focus,
|
|
||||||
propagte,
|
|
||||||
el: vnode.el,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function unbindValue(value, el) {
|
|
||||||
for (let key in value) {
|
|
||||||
const k = encodeKey(value[key])
|
|
||||||
const idxElm = mapFunctions[k].el.indexOf(el)
|
|
||||||
|
|
||||||
if (mapFunctions[k].el.length > 1 && idxElm > -1) {
|
|
||||||
mapFunctions[k].el.splice(idxElm, 1)
|
|
||||||
} else {
|
|
||||||
delete mapFunctions[k]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function availableElement(decodedKey) {
|
|
||||||
const objectIsAvoided = !!objAvoided.find(
|
|
||||||
(r) => r === document.activeElement,
|
|
||||||
)
|
|
||||||
const filterAvoided = !!elementAvoided.find(
|
|
||||||
(selector) =>
|
|
||||||
document.activeElement && document.activeElement.matches(selector),
|
|
||||||
)
|
|
||||||
return !!mapFunctions[decodedKey] && !(objectIsAvoided || filterAvoided)
|
|
||||||
}
|
|
||||||
|
|
||||||
function keyDownListener(pKey) {
|
|
||||||
const decodedKey = decodeKey(pKey)
|
|
||||||
|
|
||||||
// Check avoidable elements
|
|
||||||
if (!availableElement(decodedKey)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mapFunctions[decodedKey].propagte) {
|
|
||||||
pKey.preventDefault()
|
|
||||||
pKey.stopPropagation()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mapFunctions[decodedKey].focus) {
|
|
||||||
keyDown(decodedKey)
|
|
||||||
keyPressed = true
|
|
||||||
} else if (!keyPressed) {
|
|
||||||
const elm = mapFunctions[decodedKey].el
|
|
||||||
elm[elm.length - 1].focus()
|
|
||||||
keyPressed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function keyUpListener(pKey) {
|
|
||||||
const decodedKey = decodeKey(pKey)
|
|
||||||
|
|
||||||
if (!availableElement(decodedKey)) {
|
|
||||||
keyPressed = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mapFunctions[decodedKey].propagte) {
|
|
||||||
pKey.preventDefault()
|
|
||||||
pKey.stopPropagation()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mapFunctions[decodedKey].once || mapFunctions[decodedKey].push) {
|
|
||||||
dispatchShortkeyEvent(decodedKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyPressed = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// register key presses that happen before mounting of directive
|
|
||||||
// if (process?.env?.NODE_ENV !== 'test') {
|
|
||||||
// (() => {
|
|
||||||
document.addEventListener('keydown', keyDownListener, true)
|
|
||||||
document.addEventListener('keyup', keyUpListener, true)
|
|
||||||
// })()
|
|
||||||
// }
|
|
||||||
|
|
||||||
function install(app, options) {
|
|
||||||
elementAvoided = [...(options && options.prevent ? options.prevent : [])]
|
|
||||||
|
|
||||||
app.directive('shortkey', {
|
|
||||||
beforeMount(el, binding, vnode) {
|
|
||||||
// Mapping the commands
|
|
||||||
const value = parseValue(binding.value)
|
|
||||||
bindValue(value, el, binding, vnode)
|
|
||||||
},
|
|
||||||
|
|
||||||
updated(el, binding, vnode) {
|
|
||||||
const oldValue = parseValue(binding.oldValue)
|
|
||||||
unbindValue(oldValue, el)
|
|
||||||
|
|
||||||
const newValue = parseValue(binding.value)
|
|
||||||
bindValue(newValue, el, binding, vnode)
|
|
||||||
},
|
|
||||||
|
|
||||||
unmounted(el, binding) {
|
|
||||||
const value = parseValue(binding.value)
|
|
||||||
unbindValue(value, el)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
install,
|
|
||||||
encodeKey,
|
|
||||||
decodeKey,
|
|
||||||
keyDown,
|
|
||||||
}
|
|
|
@ -35,15 +35,17 @@ import ListSettingDuplicate from '../views/list/settings/duplicate'
|
||||||
import ListSettingShare from '../views/list/settings/share'
|
import ListSettingShare from '../views/list/settings/share'
|
||||||
import ListSettingDelete from '../views/list/settings/delete'
|
import ListSettingDelete from '../views/list/settings/delete'
|
||||||
import ListSettingArchive from '../views/list/settings/archive'
|
import ListSettingArchive from '../views/list/settings/archive'
|
||||||
import FilterSettingEdit from '../views/filters/settings/edit'
|
|
||||||
import FilterSettingDelete from '../views/filters/settings/delete'
|
|
||||||
// Namespace Settings
|
// Namespace Settings
|
||||||
import NamespaceSettingEdit from '../views/namespaces/settings/edit'
|
import NamespaceSettingEdit from '../views/namespaces/settings/edit'
|
||||||
import NamespaceSettingShare from '../views/namespaces/settings/share'
|
import NamespaceSettingShare from '../views/namespaces/settings/share'
|
||||||
import NamespaceSettingArchive from '../views/namespaces/settings/archive'
|
import NamespaceSettingArchive from '../views/namespaces/settings/archive'
|
||||||
import NamespaceSettingDelete from '../views/namespaces/settings/delete'
|
import NamespaceSettingDelete from '../views/namespaces/settings/delete'
|
||||||
|
|
||||||
// Saved Filters
|
// Saved Filters
|
||||||
import CreateSavedFilter from '../views/filters/CreateSavedFilter'
|
import FilterNew from '@/views/filters/FilterNew'
|
||||||
|
import FilterEdit from '@/views/filters/FilterEdit'
|
||||||
|
import FilterDelete from '@/views/filters/FilterDelete'
|
||||||
|
|
||||||
const PasswordResetComponent = () => import('../views/user/PasswordReset')
|
const PasswordResetComponent = () => import('../views/user/PasswordReset')
|
||||||
const GetPasswordResetComponent = () => import('../views/user/RequestPasswordReset')
|
const GetPasswordResetComponent = () => import('../views/user/RequestPasswordReset')
|
||||||
|
@ -123,6 +125,7 @@ const router = createRouter({
|
||||||
path: '/user/settings',
|
path: '/user/settings',
|
||||||
name: 'user.settings',
|
name: 'user.settings',
|
||||||
component: UserSettingsComponent,
|
component: UserSettingsComponent,
|
||||||
|
redirect: {name: 'user.settings.general'},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/user/settings/avatar',
|
path: '/user/settings/avatar',
|
||||||
|
@ -279,14 +282,14 @@ const router = createRouter({
|
||||||
path: '/lists/:listId/settings/edit',
|
path: '/lists/:listId/settings/edit',
|
||||||
name: 'filter.settings.edit',
|
name: 'filter.settings.edit',
|
||||||
components: {
|
components: {
|
||||||
popup: FilterSettingEdit,
|
popup: FilterEdit,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/lists/:listId/settings/delete',
|
path: '/lists/:listId/settings/delete',
|
||||||
name: 'filter.settings.delete',
|
name: 'filter.settings.delete',
|
||||||
components: {
|
components: {
|
||||||
popup: FilterSettingDelete,
|
popup: FilterDelete,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -337,12 +340,12 @@ const router = createRouter({
|
||||||
{
|
{
|
||||||
path: '/lists/:listId/settings/edit',
|
path: '/lists/:listId/settings/edit',
|
||||||
name: 'filter.list.settings.edit',
|
name: 'filter.list.settings.edit',
|
||||||
component: FilterSettingEdit,
|
component: FilterEdit,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/lists/:listId/settings/delete',
|
path: '/lists/:listId/settings/delete',
|
||||||
name: 'filter.list.settings.delete',
|
name: 'filter.list.settings.delete',
|
||||||
component: FilterSettingDelete,
|
component: FilterDelete,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -389,12 +392,12 @@ const router = createRouter({
|
||||||
{
|
{
|
||||||
path: '/lists/:listId/settings/edit',
|
path: '/lists/:listId/settings/edit',
|
||||||
name: 'filter.gantt.settings.edit',
|
name: 'filter.gantt.settings.edit',
|
||||||
component: FilterSettingEdit,
|
component: FilterEdit,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/lists/:listId/settings/delete',
|
path: '/lists/:listId/settings/delete',
|
||||||
name: 'filter.gantt.settings.delete',
|
name: 'filter.gantt.settings.delete',
|
||||||
component: FilterSettingDelete,
|
component: FilterDelete,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -436,12 +439,12 @@ const router = createRouter({
|
||||||
{
|
{
|
||||||
path: '/lists/:listId/settings/edit',
|
path: '/lists/:listId/settings/edit',
|
||||||
name: 'filter.table.settings.edit',
|
name: 'filter.table.settings.edit',
|
||||||
component: FilterSettingEdit,
|
component: FilterEdit,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/lists/:listId/settings/delete',
|
path: '/lists/:listId/settings/delete',
|
||||||
name: 'filter.table.settings.delete',
|
name: 'filter.table.settings.delete',
|
||||||
component: FilterSettingDelete,
|
component: FilterDelete,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -488,12 +491,12 @@ const router = createRouter({
|
||||||
{
|
{
|
||||||
path: '/lists/:listId/settings/edit',
|
path: '/lists/:listId/settings/edit',
|
||||||
name: 'filter.kanban.settings.edit',
|
name: 'filter.kanban.settings.edit',
|
||||||
component: FilterSettingEdit,
|
component: FilterEdit,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/lists/:listId/settings/delete',
|
path: '/lists/:listId/settings/delete',
|
||||||
name: 'filter.kanban.settings.delete',
|
name: 'filter.kanban.settings.delete',
|
||||||
component: FilterSettingDelete,
|
component: FilterDelete,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -542,7 +545,7 @@ const router = createRouter({
|
||||||
path: '/filters/new',
|
path: '/filters/new',
|
||||||
name: 'filters.create',
|
name: 'filters.create',
|
||||||
components: {
|
components: {
|
||||||
popup: CreateSavedFilter,
|
popup: FilterNew,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,6 +19,7 @@ import attachments from './modules/attachments'
|
||||||
import labels from './modules/labels'
|
import labels from './modules/labels'
|
||||||
|
|
||||||
import ListService from '../services/list'
|
import ListService from '../services/list'
|
||||||
|
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
|
||||||
|
|
||||||
export const store = createStore({
|
export const store = createStore({
|
||||||
strict: import.meta.env.DEV,
|
strict: import.meta.env.DEV,
|
||||||
|
@ -43,6 +44,7 @@ export const store = createStore({
|
||||||
menuActive: true,
|
menuActive: true,
|
||||||
keyboardShortcutsActive: false,
|
keyboardShortcutsActive: false,
|
||||||
quickActionsActive: false,
|
quickActionsActive: false,
|
||||||
|
vikunjaReady: false,
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
[LOADING](state, loading) {
|
[LOADING](state, loading) {
|
||||||
|
@ -84,6 +86,9 @@ export const store = createStore({
|
||||||
[BACKGROUND](state, background) {
|
[BACKGROUND](state, background) {
|
||||||
state.background = background
|
state.background = background
|
||||||
},
|
},
|
||||||
|
vikunjaReady(state, ready) {
|
||||||
|
state.vikunjaReady = ready
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async [CURRENT_LIST]({state, commit}, currentList) {
|
async [CURRENT_LIST]({state, commit}, currentList) {
|
||||||
|
@ -138,5 +143,10 @@ export const store = createStore({
|
||||||
|
|
||||||
commit(CURRENT_LIST, currentList)
|
commit(CURRENT_LIST, currentList)
|
||||||
},
|
},
|
||||||
|
async loadApp({commit, dispatch}) {
|
||||||
|
await checkAndSetApiUrl(window.API_URL)
|
||||||
|
await dispatch('auth/checkAuth')
|
||||||
|
commit('vikunjaReady', true)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue