forked from vikunja/frontend
Compare commits
106 Commits
c30c467e30
...
4319b4ff4a
Author | SHA1 | Date | |
---|---|---|---|
4319b4ff4a | |||
6cde8e2640 | |||
5c6fcffd75 | |||
9b243873c5 | |||
dc347ed8ba | |||
d0d1086dac | |||
fb91b71395 | |||
|
b688f35446 | ||
|
91580f97a1 | ||
d95fc32d67 | |||
|
6b5ac20ef8 | ||
|
66f0df5037 | ||
|
981babd691 | ||
46fa43d67f | |||
4ef54f1bc2 | |||
|
b029889f27 | ||
|
44f8e3ea9b | ||
|
ae36c041a7 | ||
8a722f294c | |||
5674acbee6 | |||
bf9371c60a | |||
181930f537 | |||
cee22a1942 | |||
673458b41d | |||
b56e99bfdf | |||
a5b5d99129 | |||
e1b9a9921c | |||
709ebdf567 | |||
d41ee3dc8c | |||
e342f6e3ed | |||
8b2450d6f9 | |||
943eab5e7e | |||
d55328e03b | |||
30aa1cd1cf | |||
01f3196938 | |||
ced8e0fd3c | |||
|
b838e7494d | ||
745b4b56ec | |||
c5b539912d | |||
|
ed6dc94873 | ||
233b9693eb | |||
|
2656c74f37 | ||
28b571588e | |||
ae5d3ecac5 | |||
26213d5e8c | |||
ae0ecb9f23 | |||
75af78eecc | |||
cf202738be | |||
|
ed78a83ed9 | ||
d0635ae4a1 | |||
552751b346 | |||
e83cf50e51 | |||
810635f5a5 | |||
213cbfb440 | |||
11e5ff42a6 | |||
bb64452382 | |||
e31d388ec1 | |||
a4dd8ec0d1 | |||
7b4b97b0d3 | |||
665cc84174 | |||
6c6ccc647e | |||
0684806db0 | |||
|
d0d4096f8b | ||
507a73e74c | |||
1fa164453c | |||
fcadbc352b | |||
7824ba089a | |||
31f344503c | |||
|
e63fd587c8 | ||
c1c6f21ad2 | |||
060057e268 | |||
4e02f77382 | |||
780ac4eb74 | |||
d46374839e | |||
ce45034776 | |||
7fada671fc | |||
feea191ecf | |||
db605e0d21 | |||
0fe433891a | |||
0a2d5ef820 | |||
31f0c384ac | |||
73651ef964 | |||
03eee061ff | |||
9a499f68e8 | |||
b5927be136 | |||
be78fc177d | |||
|
30cc89fe25 | ||
|
8e6e52bf02 | ||
|
20e059c921 | ||
36f1e846bb | |||
1e50aeebd8 | |||
05e624ce39 | |||
4e6b9f4cbd | |||
17bc35864b | |||
52c115cea8 | |||
df1a7dd19e | |||
34994e919b | |||
d6669fa8e2 | |||
6d86d4ce59 | |||
15da2657d3 | |||
1c23a8c570 | |||
054f804427 | |||
052cd36085 | |||
e49fd16a3a | |||
c92b59db1d | |||
1cef4f6e0b |
29
.drone.yml
29
.drone.yml
|
@ -1,8 +1,7 @@
|
|||
---
|
||||
kind: pipeline
|
||||
name: build
|
||||
|
||||
# TODO: update translations only nightly
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
include:
|
||||
|
@ -138,6 +137,26 @@ steps:
|
|||
- failure
|
||||
- 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
|
||||
name: release-latest
|
||||
|
@ -183,6 +202,7 @@ steps:
|
|||
YARN_CACHE_FOLDER: .cache/yarn/
|
||||
commands:
|
||||
- yarn --frozen-lockfile --network-timeout 100000
|
||||
- npx browserslist@latest --update-db
|
||||
- yarn run lint
|
||||
- "echo '{\"VERSION\": \"'$(git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')'\"}' > src/version.json"
|
||||
- yarn run build
|
||||
|
@ -635,3 +655,8 @@ steps:
|
|||
environment:
|
||||
CROWDIN_KEY:
|
||||
from_secret: crowdin_key
|
||||
---
|
||||
kind: signature
|
||||
hmac: 188ee90100c5fc5922a445e531e7a47453121edddb2a64a182eb23ed2bf602de
|
||||
|
||||
...
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -26,3 +26,6 @@ stats.html
|
|||
# Test files
|
||||
cypress/screenshots
|
||||
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.
|
||||
|
||||
## [0.18.2] - 2021-11-23
|
||||
|
||||
### Fixed
|
||||
|
||||
* fix(docker): properly replace api url
|
||||
* fix: edit saved filter title
|
||||
|
||||
## [0.18.1] - 2021-09-08
|
||||
|
||||
### Added
|
||||
|
|
|
@ -24,12 +24,6 @@ RUN \
|
|||
# Stage 2: copy
|
||||
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 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)
|
||||
[![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)
|
||||
|
||||
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')
|
||||
.contains('Columns')
|
||||
.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')
|
||||
.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')
|
||||
.click()
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ describe('The Menu', () => {
|
|||
})
|
||||
|
||||
it('Can be hidden on desktop', () => {
|
||||
cy.get('a.menu-show-button:visible')
|
||||
cy.get('button.menu-show-button:visible')
|
||||
.click()
|
||||
cy.get('.namespace-container')
|
||||
.should('not.have.class', 'is-active')
|
||||
|
@ -21,7 +21,7 @@ describe('The Menu', () => {
|
|||
|
||||
it('Is can be shown on mobile', () => {
|
||||
cy.viewport('iphone-8')
|
||||
cy.get('a.menu-show-button:visible')
|
||||
cy.get('button.menu-show-button:visible')
|
||||
.click()
|
||||
cy.get('.namespace-container')
|
||||
.should('have.class', 'is-active')
|
||||
|
|
|
@ -263,8 +263,7 @@ describe('Task', () => {
|
|||
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Assign this task to a user')
|
||||
cy.get('[data-cy="taskDetail.assign"]')
|
||||
.click()
|
||||
cy.get('.task-view .column.assignees .multiselect input')
|
||||
.type(users[1].username)
|
||||
|
|
|
@ -24,8 +24,8 @@ context('Registration', () => {
|
|||
cy.visit('/register')
|
||||
cy.get('#username').type(fixture.username)
|
||||
cy.get('#email').type(fixture.email)
|
||||
cy.get('#password1').type(fixture.password)
|
||||
cy.get('#password2').type(fixture.password)
|
||||
cy.get('#password').type(fixture.password)
|
||||
cy.get('#passwordValidation').type(fixture.password)
|
||||
cy.get('#register-submit').click()
|
||||
cy.url().should('include', '/')
|
||||
cy.clock(1625656161057) // 13:00
|
||||
|
@ -42,8 +42,8 @@ context('Registration', () => {
|
|||
cy.visit('/register')
|
||||
cy.get('#username').type(fixture.username)
|
||||
cy.get('#email').type(fixture.email)
|
||||
cy.get('#password1').type(fixture.password)
|
||||
cy.get('#password2').type(fixture.password)
|
||||
cy.get('#password').type(fixture.password)
|
||||
cy.get('#passwordValidation').type(fixture.password)
|
||||
cy.get('#register-submit').click()
|
||||
cy.get('div.notification.is-danger').contains('A user with this username already exists.')
|
||||
})
|
||||
|
|
15
netlify.toml
Normal file
15
netlify.toml
Normal file
|
@ -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 {
|
||||
listen 80;
|
||||
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 _;
|
||||
|
||||
expires $expires;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/dummy.crt;
|
||||
ssl_certificate_key /etc/nginx/ssl/dummy.key;
|
||||
location ~* .(txt|webmanifest|css|js|mjs|map|svg|jpg|jpeg|png|ico|ttf|woff|woff2|wav)$ {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri $uri/ /;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
index index.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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@github/hotkey": "1.6.0",
|
||||
"@kyvg/vue3-notification": "2.3.4",
|
||||
"@sentry/tracing": "6.14.1",
|
||||
"@sentry/vue": "6.14.1",
|
||||
"@vue/compat": "3.2.21",
|
||||
"bulma": "0.9.3",
|
||||
"@sentry/tracing": "6.15.0",
|
||||
"@sentry/vue": "6.15.0",
|
||||
"@vue/compat": "3.2.22",
|
||||
"@vueuse/core": "7.0.1",
|
||||
"bulma-css-variables": "0.9.33",
|
||||
"camel-case": "4.1.2",
|
||||
"codemirror": "5.63.3",
|
||||
"codemirror": "5.64.0",
|
||||
"copy-to-clipboard": "3.3.1",
|
||||
"date-fns": "2.25.0",
|
||||
"date-fns": "2.26.0",
|
||||
"dompurify": "2.3.3",
|
||||
"easymde": "2.15.0",
|
||||
"flatpickr": "4.6.9",
|
||||
"flexsearch": "0.7.21",
|
||||
"highlight.js": "11.3.1",
|
||||
"is-touch-device": "1.0.1",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"marked": "4.0.0",
|
||||
"marked": "4.0.4",
|
||||
"register-service-worker": "1.7.2",
|
||||
"snake-case": "3.0.4",
|
||||
"ufo": "0.7.9",
|
||||
"vue": "3.2.21",
|
||||
"vue-advanced-cropper": "2.6.3",
|
||||
"v-tooltip": "4.0.0-beta.2",
|
||||
"vue": "3.2.22",
|
||||
"vue-advanced-cropper": "2.7.0",
|
||||
"vue-drag-resize": "2.0.3",
|
||||
"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",
|
||||
"vuedraggable": "4.1.0",
|
||||
"vuex": "4.0.2",
|
||||
"workbox-precaching": "6.3.0"
|
||||
"workbox-precaching": "6.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@4tw/cypress-drag-drop": "2.0.0",
|
||||
|
@ -53,34 +57,38 @@
|
|||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||
"@fortawesome/vue-fontawesome": "3.0.0-5",
|
||||
"@types/jest": "27.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.3.1",
|
||||
"@typescript-eslint/parser": "5.3.1",
|
||||
"@vitejs/plugin-legacy": "1.6.2",
|
||||
"@vitejs/plugin-vue": "1.9.4",
|
||||
"@vue/eslint-config-typescript": "9.0.1",
|
||||
"autoprefixer": "10.4.0",
|
||||
"@types/flexsearch": "0.7.2",
|
||||
"@types/jest": "27.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.4.0",
|
||||
"@typescript-eslint/parser": "5.4.0",
|
||||
"@vitejs/plugin-legacy": "1.6.3",
|
||||
"@vitejs/plugin-vue": "1.10.0",
|
||||
"@vue/eslint-config-typescript": "9.1.0",
|
||||
"axios": "0.24.0",
|
||||
"browserslist": "4.17.6",
|
||||
"browserslist": "4.18.1",
|
||||
"cypress": "8.7.0",
|
||||
"cypress-file-upload": "5.0.8",
|
||||
"esbuild": "0.13.13",
|
||||
"eslint": "8.2.0",
|
||||
"eslint-plugin-vue": "8.0.3",
|
||||
"esbuild": "0.13.15",
|
||||
"eslint": "8.3.0",
|
||||
"eslint-plugin-vue": "8.1.1",
|
||||
"express": "4.17.1",
|
||||
"faker": "5.5.3",
|
||||
"jest": "27.3.1",
|
||||
"netlify-cli": "7.1.0",
|
||||
"postcss": "8.3.11",
|
||||
"rollup": "2.59.0",
|
||||
"postcss-preset-env": "7.0.1",
|
||||
"rollup": "2.60.1",
|
||||
"rollup-plugin-visualizer": "5.5.2",
|
||||
"sass": "1.43.4",
|
||||
"slugify": "1.6.3",
|
||||
"ts-jest": "27.0.7",
|
||||
"typescript": "4.4.4",
|
||||
"typescript": "4.5.2",
|
||||
"vite": "2.6.14",
|
||||
"vite-plugin-pwa": "0.11.3",
|
||||
"vue-tsc": "0.29.3",
|
||||
"vite-plugin-pwa": "0.11.7",
|
||||
"vite-svg-loader": "3.1.0",
|
||||
"vue-tsc": "0.29.6",
|
||||
"wait-on": "6.0.0",
|
||||
"workbox-cli": "6.3.0"
|
||||
"workbox-cli": "6.4.1"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
|
66
scripts/deploy-preview-netlify.js
Normal file
66
scripts/deploy-preview-netlify.js
Normal file
|
@ -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}!`)
|
||||
})()
|
1
scripts/deploy-preview-netlify.js.sha384
Normal file
1
scripts/deploy-preview-netlify.js.sha384
Normal file
|
@ -0,0 +1 @@
|
|||
55ce0faaa2c1919341617ccfaeccbb6029ac12107964ff488985cff13dd952f1a991df3ab0d4b0705deb761e508e6434 ./scripts/deploy-preview-netlify.js
|
55
src/App.vue
55
src/App.vue
|
@ -1,25 +1,21 @@
|
|||
<template>
|
||||
<ready>
|
||||
<div :class="{'is-touch': isTouch}">
|
||||
<div :class="{'is-hidden': !online}">
|
||||
<!-- 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>
|
||||
<top-navigation v-if="authUser"/>
|
||||
<content-auth v-if="authUser"/>
|
||||
<template v-if="authUser">
|
||||
<top-navigation/>
|
||||
<content-auth/>
|
||||
</template>
|
||||
<content-link-share v-else-if="authLinkShare"/>
|
||||
<content-no-auth v-else/>
|
||||
<notification/>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<transition name="fade">
|
||||
<keyboard-shortcuts v-if="keyboardShortcutsActive"/>
|
||||
</transition>
|
||||
</div>
|
||||
</ready>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -36,6 +32,8 @@ import ContentLinkShare from './components/home/contentLinkShare'
|
|||
import ContentNoAuth from './components/home/contentNoAuth'
|
||||
import {setLanguage} from './i18n'
|
||||
import AccountDeleteService from '@/services/accountDelete'
|
||||
import Ready from '@/components/misc/ready'
|
||||
import {useColorScheme} from '@/composables/useColorScheme'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'app',
|
||||
|
@ -46,6 +44,7 @@ export default defineComponent({
|
|||
TopNavigation,
|
||||
KeyboardShortcuts,
|
||||
Notification,
|
||||
Ready,
|
||||
},
|
||||
beforeMount() {
|
||||
this.setupOnlineStatus()
|
||||
|
@ -54,15 +53,11 @@ export default defineComponent({
|
|||
this.setupAccountDeletionVerification()
|
||||
},
|
||||
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()
|
||||
},
|
||||
setup() {
|
||||
useColorScheme()
|
||||
},
|
||||
created() {
|
||||
// Make sure to always load the home route when running with electron
|
||||
if (this.$route.fullPath.endsWith('frontend/index.html')) {
|
||||
|
@ -121,29 +116,3 @@ export default defineComponent({
|
|||
<style lang="scss">
|
||||
@import '@/styles/global.scss';
|
||||
</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="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)"/>
|
||||
|
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
17
src/components/home/Logo.vue
Normal file
17
src/components/home/Logo.vue
Normal file
|
@ -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>
|
77
src/components/home/MenuButton.vue
Normal file
77
src/components/home/MenuButton.vue
Normal file
|
@ -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>
|
20
src/components/home/PoweredByLink.vue
Normal file
20
src/components/home/PoweredByLink.vue
Normal file
|
@ -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>
|
||||
<div>
|
||||
<a @click="$store.commit('menuActive', false)" class="menu-hide-button" v-if="menuActive">
|
||||
<icon icon="times"></icon>
|
||||
<icon icon="times" />
|
||||
</a>
|
||||
<div
|
||||
:class="{'has-background': background}"
|
||||
|
@ -31,8 +31,7 @@
|
|||
<a
|
||||
class="keyboard-shortcuts-button"
|
||||
@click="showKeyboardShortcuts()"
|
||||
@shortkey="showKeyboardShortcuts()"
|
||||
v-shortkey="['?']"
|
||||
v-shortcut="'?'"
|
||||
>
|
||||
<icon icon="keyboard"/>
|
||||
</a>
|
||||
|
@ -134,6 +133,32 @@ export default {
|
|||
</script>
|
||||
|
||||
<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 {
|
||||
min-height: calc(100vh - 65px);
|
||||
|
||||
|
@ -166,7 +191,7 @@ export default {
|
|||
}
|
||||
|
||||
.card {
|
||||
background: $white;
|
||||
background: var(--white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,7 +220,7 @@ export default {
|
|||
right: 1rem;
|
||||
z-index: 4500; // The modal has a z-index of 4000
|
||||
|
||||
color: $grey-500;
|
||||
color: var(--grey-500);
|
||||
transition: color $transition;
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
>
|
||||
<div class="container has-text-centered link-share-view">
|
||||
<div class="column is-10 is-offset-1">
|
||||
<img alt="Vikunja" class="logo" :src="logoUrl" />
|
||||
<Logo class="logo" />
|
||||
<h1
|
||||
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
|
||||
class="title">
|
||||
|
@ -14,9 +14,7 @@
|
|||
</h1>
|
||||
<div class="box has-text-left view">
|
||||
<router-view/>
|
||||
<a class="menu-bottom-link" href="https://vikunja.io" target="_blank" rel="noreferrer noopener nofollow">
|
||||
{{ $t('misc.poweredBy') }}
|
||||
</a>
|
||||
<PoweredByLink />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -25,35 +23,29 @@
|
|||
|
||||
<script>
|
||||
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 {
|
||||
name: 'contentLinkShare',
|
||||
data() {
|
||||
return {
|
||||
logoUrl,
|
||||
}
|
||||
components: {
|
||||
Logo,
|
||||
PoweredByLink,
|
||||
},
|
||||
computed: mapState({
|
||||
currentList: CURRENT_LIST,
|
||||
background: 'background',
|
||||
}),
|
||||
computed: mapState([
|
||||
'currentList',
|
||||
'background',
|
||||
]),
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.link-share-container.has-background .view {
|
||||
background: transparent;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
|
||||
.logout .button {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.link-share-view {
|
||||
.logo {
|
||||
max-width: 300px;
|
||||
width: 90%;
|
||||
|
@ -64,12 +56,12 @@ export default {
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: $white;
|
||||
.title {
|
||||
text-shadow: 0 0 1rem var(--white);
|
||||
}
|
||||
|
||||
.title {
|
||||
text-shadow: 0 0 1rem $white;
|
||||
}
|
||||
// FIXME: this should be defined somewhere deep
|
||||
.link-share-view .card {
|
||||
background-color: var(--white);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,40 +1,20 @@
|
|||
<template>
|
||||
<div class="no-auth-wrapper">
|
||||
<div class="noauth-container">
|
||||
<img alt="Vikunja" :src="logoUrl" 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>
|
||||
<no-auth-wrapper>
|
||||
<router-view/>
|
||||
</div>
|
||||
</div>
|
||||
</no-auth-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
|
||||
import logoUrl from '@/assets/logo-full.svg'
|
||||
import {saveLastVisited} from '@/helpers/saveLastVisited'
|
||||
import NoAuthWrapper from '@/components/misc/no-auth-wrapper'
|
||||
|
||||
export default {
|
||||
name: 'contentNoAuth',
|
||||
data() {
|
||||
return {
|
||||
logoUrl,
|
||||
}
|
||||
},
|
||||
components: {NoAuthWrapper},
|
||||
computed: {
|
||||
routeName() {
|
||||
return this.$route.name
|
||||
},
|
||||
...mapState({
|
||||
motd: state => state.config.motd,
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
routeName: {
|
||||
|
@ -65,17 +45,3 @@ export default {
|
|||
},
|
||||
}
|
||||
</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="menu top-menu">
|
||||
<router-link :to="{name: 'home'}" class="logo">
|
||||
<img alt="Vikunja" :src="logoUrl" width="164" height="48"/>
|
||||
<Logo width="164" height="48" />
|
||||
</router-link>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
|
@ -48,7 +48,7 @@
|
|||
</ul>
|
||||
</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" >
|
||||
<div class="namespace-title" :class="{'has-menu': n.id > 0}">
|
||||
<span
|
||||
|
@ -105,7 +105,7 @@
|
|||
>
|
||||
<template #item="{element: l}">
|
||||
<li
|
||||
class="loader-container"
|
||||
class="loader-container is-loading-small"
|
||||
:class="{'is-loading': listUpdating[l.id]}"
|
||||
>
|
||||
<router-link
|
||||
|
@ -146,25 +146,34 @@
|
|||
</div>
|
||||
</template>
|
||||
</aside>
|
||||
<a class="menu-bottom-link" :href="poweredByUrl" target="_blank" rel="noreferrer noopener nofollow">
|
||||
{{ $t('misc.poweredBy') }}
|
||||
</a>
|
||||
<PoweredByLink />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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 NamespaceSettingsDropdown from '@/components/namespace/namespace-settings-dropdown.vue'
|
||||
import draggable from 'vuedraggable'
|
||||
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
|
||||
import {POWERED_BY} from '@/urls'
|
||||
import PoweredByLink from '@/components/home/PoweredByLink.vue'
|
||||
import Logo from '@/components/home/Logo.vue'
|
||||
|
||||
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 {
|
||||
name: 'navigation',
|
||||
|
||||
components: {
|
||||
ListSettingsDropdown,
|
||||
NamespaceSettingsDropdown,
|
||||
draggable,
|
||||
Logo,
|
||||
PoweredByLink,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
listsVisible: {},
|
||||
|
@ -174,15 +183,8 @@ export default {
|
|||
ghostClass: 'ghost',
|
||||
},
|
||||
listUpdating: {},
|
||||
logoUrl,
|
||||
poweredByUrl: POWERED_BY,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ListSettingsDropdown,
|
||||
NamespaceSettingsDropdown,
|
||||
draggable,
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
namespaces: state => state.namespaces.namespaces.filter(n => !n.isArchived),
|
||||
|
@ -278,8 +280,8 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
$navbar-padding: 2rem;
|
||||
$vikunja-nav-background: $light-background;
|
||||
$vikunja-nav-color: $grey-700;
|
||||
$vikunja-nav-background: var(--site-background);
|
||||
$vikunja-nav-color: var(--grey-700);
|
||||
$vikunja-nav-selected-width: 0.4rem;
|
||||
|
||||
.namespace-container {
|
||||
|
@ -347,12 +349,12 @@ $vikunja-nav-selected-width: 0.4rem;
|
|||
opacity: 0;
|
||||
|
||||
&:hover {
|
||||
color: $orange;
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
&.is-favorite {
|
||||
opacity: 1;
|
||||
color: $orange;
|
||||
color: var(--warning);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -434,7 +436,7 @@ $vikunja-nav-selected-width: 0.4rem;
|
|||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background: $white;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
:deep(.dropdown-trigger) {
|
||||
|
@ -447,14 +449,6 @@ $vikunja-nav-selected-width: 0.4rem;
|
|||
&:hover :deep(.dropdown-trigger) {
|
||||
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 {
|
||||
|
@ -462,7 +456,7 @@ $vikunja-nav-selected-width: 0.4rem;
|
|||
}
|
||||
|
||||
.ghost {
|
||||
background: $grey-200;
|
||||
background: var(--grey-200);
|
||||
|
||||
* {
|
||||
opacity: 0;
|
||||
|
@ -502,25 +496,28 @@ $vikunja-nav-selected-width: 0.4rem;
|
|||
}
|
||||
|
||||
&.router-link-exact-active {
|
||||
color: $primary;
|
||||
border-left: $vikunja-nav-selected-width solid $primary;
|
||||
color: var(--primary);
|
||||
border-left: $vikunja-nav-selected-width solid var(--primary);
|
||||
|
||||
.icon {
|
||||
color: $primary;
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-left: $vikunja-nav-selected-width solid $primary;
|
||||
border-left: $vikunja-nav-selected-width solid var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: none;
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
display: block;
|
||||
|
||||
padding-left: 2rem;
|
||||
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);
|
||||
}
|
||||
|
||||
&.loader-container.is-loading:after {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
top: calc(50% - .75rem);
|
||||
left: calc(50% - .75rem);
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: $grey-400 !important;
|
||||
color: var(--grey-400) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,24 +5,11 @@
|
|||
class="navbar main-theme is-fixed-top"
|
||||
role="navigation"
|
||||
>
|
||||
<div class="navbar-brand">
|
||||
<router-link :to="{name: 'home'}" class="navbar-item logo">
|
||||
<img width="164" height="48" alt="Vikunja" :src="logoUrl" />
|
||||
<router-link :to="{name: 'home'}" class="logo-link">
|
||||
<Logo width="164" height="48"/>
|
||||
</router-link>
|
||||
<a
|
||||
@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'}">
|
||||
<MenuButton class="menu-button"/>
|
||||
<div class="list-title" ref="listTitle" v-show="currentList.id">
|
||||
<template v-if="currentList.id">
|
||||
<h1
|
||||
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
|
||||
|
@ -39,8 +26,8 @@
|
|||
<a
|
||||
@click="openQuickActions"
|
||||
class="trigger-button pr-0"
|
||||
@shortkey="openQuickActions"
|
||||
v-shortkey="['ctrl', 'k']"
|
||||
v-shortcut="'Control+k'"
|
||||
:title="$t('keyboardShortcuts.quickSearch')"
|
||||
>
|
||||
<icon icon="search"/>
|
||||
</a>
|
||||
|
@ -101,9 +88,8 @@ import Update from '@/components/home/update.vue'
|
|||
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
|
||||
import Dropdown from '@/components/misc/dropdown.vue'
|
||||
import Notifications from '@/components/notifications/notifications.vue'
|
||||
|
||||
import logoUrl from '@/assets/logo-full.svg'
|
||||
import logoFullPrideUrl from '@/assets/logo-full-pride.svg'
|
||||
import Logo from '@/components/home/Logo.vue'
|
||||
import MenuButton from '@/components/home/MenuButton.vue'
|
||||
|
||||
export default {
|
||||
name: 'topNavigation',
|
||||
|
@ -112,11 +98,10 @@ export default {
|
|||
Dropdown,
|
||||
ListSettingsDropdown,
|
||||
Update,
|
||||
Logo,
|
||||
MenuButton,
|
||||
},
|
||||
computed: {
|
||||
logoUrl() {
|
||||
return (new Date()).getMonth() === 5 ? logoFullPrideUrl : logoUrl
|
||||
},
|
||||
...mapState({
|
||||
userInfo: state => state.auth.info,
|
||||
userAvatar: state => state.auth.avatarUrl,
|
||||
|
@ -152,27 +137,39 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
$vikunja-nav-logo-full-width: 164px;
|
||||
$user-dropdown-width-mobile: 5rem;
|
||||
|
||||
$hamburger-menu-icon-spacing: 1rem;
|
||||
$hamburger-menu-icon-width: 28px;
|
||||
|
||||
.navbar {
|
||||
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;
|
||||
align-items: center;
|
||||
padding-left: 2rem;
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-button {
|
||||
align-self: stretch;
|
||||
margin-right: auto;
|
||||
|
||||
.logo img {
|
||||
width: $vikunja-nav-logo-full-width;
|
||||
}
|
||||
}
|
||||
&.is-dark .navbar-brand > .navbar-item {
|
||||
@media screen and (max-width: $tablet) {
|
||||
margin: 0 auto;
|
||||
}
|
||||
margin-left: $hamburger-menu-icon-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar.main-theme {
|
||||
background: $light-background;
|
||||
background: var(--site-background);
|
||||
z-index: 5 !important;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
@ -197,10 +194,6 @@ $vikunja-nav-logo-full-width: 164px;
|
|||
}
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
.navbar-brand {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.user {
|
||||
width: $user-dropdown-width-mobile;
|
||||
display: flex;
|
||||
|
@ -231,7 +224,7 @@ $vikunja-nav-logo-full-width: 164px;
|
|||
:deep() {
|
||||
.trigger-button {
|
||||
cursor: pointer;
|
||||
color: $grey-400;
|
||||
color: var(--grey-400);
|
||||
padding: .5rem;
|
||||
font-size: 1.25rem;
|
||||
position: relative;
|
||||
|
@ -290,7 +283,7 @@ $vikunja-nav-logo-full-width: 164px;
|
|||
}
|
||||
|
||||
:deep(.dropdown-trigger) {
|
||||
color: $grey-400;
|
||||
color: var(--grey-400);
|
||||
margin-left: 1rem;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
|
|
|
@ -57,7 +57,7 @@ export default {
|
|||
padding: 0 0 0 .5rem;
|
||||
border-radius: $radius;
|
||||
font-size: .9rem;
|
||||
color: $grey-900;
|
||||
color: var(--grey-900);
|
||||
justify-content: space-between;
|
||||
|
||||
@media screen and (max-width: $desktop) {
|
||||
|
|
|
@ -82,11 +82,11 @@ export default {
|
|||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
height: $button-height;
|
||||
box-shadow: $shadow-sm;
|
||||
box-shadow: var(--shadow-sm);
|
||||
|
||||
&.is-hovered,
|
||||
&:hover {
|
||||
box-shadow: $shadow-md;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
&.fullheight {
|
||||
|
@ -99,11 +99,11 @@ export default {
|
|||
&:active,
|
||||
&:focus,
|
||||
&:focus:not(:active) {
|
||||
box-shadow: $shadow-xs !important;
|
||||
box-shadow: var(--shadow-xs) !important;
|
||||
}
|
||||
|
||||
&.is-primary.is-outlined:hover {
|
||||
color: $white;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
&.is-small {
|
||||
|
|
|
@ -134,7 +134,7 @@ export default {
|
|||
height: $PICKER_SIZE;
|
||||
overflow: hidden;
|
||||
border-radius: 100%;
|
||||
border: $BORDER_WIDTH solid $grey-300;
|
||||
border: $BORDER_WIDTH solid var(--grey-300);
|
||||
box-shadow: $shadow;
|
||||
|
||||
& > * {
|
||||
|
|
|
@ -258,7 +258,7 @@ export default {
|
|||
position: absolute;
|
||||
z-index: 99;
|
||||
width: 320px;
|
||||
background: $white;
|
||||
background: var(--white);
|
||||
border-radius: $radius;
|
||||
box-shadow: $shadow;
|
||||
|
||||
|
@ -272,7 +272,7 @@ export default {
|
|||
padding: 0 .5rem;
|
||||
width: 100%;
|
||||
height: 2.25rem;
|
||||
color: $text;
|
||||
color: var(--text);
|
||||
transition: all $transition;
|
||||
|
||||
&:first-child {
|
||||
|
@ -280,7 +280,7 @@ export default {
|
|||
}
|
||||
|
||||
&:hover {
|
||||
background: $light;
|
||||
background: var(--light);
|
||||
}
|
||||
|
||||
.text {
|
||||
|
@ -291,7 +291,7 @@ export default {
|
|||
padding-right: .25rem;
|
||||
|
||||
.weekday {
|
||||
color: $text-light;
|
||||
color: var(--text-light);
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -338,13 +338,14 @@ $editor-border-color: #ddd;
|
|||
.CodeMirror {
|
||||
padding: .5rem;
|
||||
border: 1px solid $editor-border-color;
|
||||
background: var(--white);
|
||||
|
||||
&-lines pre {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
&-placeholder {
|
||||
color: $grey-400 !important;
|
||||
color: var(--grey-400) !important;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
@ -383,7 +384,7 @@ $editor-border-color: #ddd;
|
|||
|
||||
pre.CodeMirror-line {
|
||||
margin-bottom: 0 !important;
|
||||
color: $grey-700 !important;
|
||||
color: var(--grey-700) !important;
|
||||
}
|
||||
|
||||
.cm-header {
|
||||
|
@ -409,10 +410,10 @@ ul.actions {
|
|||
}
|
||||
|
||||
&, a {
|
||||
color: $grey-500;
|
||||
color: var(--grey-500);
|
||||
|
||||
&.done-edit {
|
||||
color: $primary;
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ svg {
|
|||
}
|
||||
|
||||
.check:hover svg {
|
||||
stroke: $primary;
|
||||
stroke: var(--primary);
|
||||
}
|
||||
|
||||
.is-disabled .check:hover svg {
|
||||
|
@ -125,7 +125,7 @@ polyline {
|
|||
|
||||
input[type=checkbox]:checked + .check {
|
||||
svg {
|
||||
stroke: $primary;
|
||||
stroke: var(--primary);
|
||||
}
|
||||
|
||||
path {
|
||||
|
|
|
@ -380,23 +380,23 @@ export default {
|
|||
|
||||
&.has-search-results .input-wrapper {
|
||||
border-radius: $radius $radius 0 0;
|
||||
border-color: $primary !important;
|
||||
background: $white !important;
|
||||
border-color: var(--primary) !important;
|
||||
background: var(--white) !important;
|
||||
|
||||
&, &:focus-within {
|
||||
border-bottom-color: $grey-200 !important;
|
||||
border-bottom-color: var(--grey-200) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
padding: 0;
|
||||
background: $white !important;
|
||||
border-color: $grey-200 !important;
|
||||
background: var(--white) !important;
|
||||
border-color: var(--grey-200) !important;
|
||||
flex-wrap: wrap;
|
||||
height: auto;
|
||||
|
||||
&:hover {
|
||||
border-color: $grey-300 !important;
|
||||
border-color: var(--grey-300) !important;
|
||||
}
|
||||
|
||||
.input {
|
||||
|
@ -422,8 +422,8 @@ export default {
|
|||
}
|
||||
|
||||
&:focus-within {
|
||||
border-color: $primary !important;
|
||||
background: $white !important;
|
||||
border-color: var(--primary) !important;
|
||||
background: var(--white) !important;
|
||||
}
|
||||
|
||||
.loader {
|
||||
|
@ -432,9 +432,9 @@ export default {
|
|||
}
|
||||
|
||||
.search-results {
|
||||
background: $white;
|
||||
background: var(--white);
|
||||
border-radius: 0 0 $radius $radius;
|
||||
border: 1px solid $primary;
|
||||
border: 1px solid var(--primary);
|
||||
border-top: none;
|
||||
|
||||
max-height: 50vh;
|
||||
|
@ -481,16 +481,16 @@ export default {
|
|||
}
|
||||
|
||||
&:focus, &:hover {
|
||||
background: $grey-100;
|
||||
background: var(--grey-100);
|
||||
box-shadow: none !important;
|
||||
|
||||
.hint-text {
|
||||
color: $text;
|
||||
color: var(--text);
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: $grey-200;
|
||||
background: var(--grey-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +1,49 @@
|
|||
<template>
|
||||
<transition name="fade">
|
||||
<x-button
|
||||
v-if="hasFilters"
|
||||
type="secondary"
|
||||
@click="clearFilters"
|
||||
>
|
||||
{{ $t('filters.clear') }}
|
||||
</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-if="visibleInternal"
|
||||
v-model="value"
|
||||
ref="filters"
|
||||
class="filter-popup"
|
||||
:class="{'is-open': isOpen}"
|
||||
/>
|
||||
</transition>
|
||||
</template>
|
||||
</popup>
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
name: 'filter-popup',
|
||||
components: {
|
||||
Popup,
|
||||
Filters,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
required: true,
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
data() {
|
||||
return {
|
||||
visibleInternal: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
value: {
|
||||
get() {
|
||||
|
@ -41,34 +53,46 @@ export default {
|
|||
this.$emit('update:modelValue', value)
|
||||
},
|
||||
},
|
||||
hasFilters() {
|
||||
// this.value also contains the page parameter which we don't want to include in filters
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const {filter_by, filter_value, filter_comparator, filter_concat, s} = this.value
|
||||
const def = {...getDefaultParams()}
|
||||
|
||||
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)
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.hidePopup)
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('click', this.hidePopup)
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
handler(value) {
|
||||
this.params = value
|
||||
this.value = value
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
visible() {
|
||||
this.visibleInternal = !this.visibleInternal
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hidePopup(e) {
|
||||
if (!this.visibleInternal) {
|
||||
return
|
||||
}
|
||||
|
||||
closeWhenClickedOutside(e, this.$refs.filters.$el, () => {
|
||||
this.visibleInternal = false
|
||||
})
|
||||
clearFilters() {
|
||||
this.value = {...getDefaultParams()}
|
||||
},
|
||||
},
|
||||
}
|
||||
</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 EditLabels from '@/components/tasks/partials/editLabels.vue'
|
||||
|
||||
import {objectToSnakeCase} from '@/helpers/case'
|
||||
|
||||
// FIXME: merge with DEFAULT_PARAMS in taskList.js
|
||||
const DEFAULT_PARAMS = {
|
||||
sort_by: [],
|
||||
|
@ -261,7 +263,9 @@ export default {
|
|||
watch: {
|
||||
modelValue: {
|
||||
handler(value) {
|
||||
this.params = value
|
||||
// FIXME: filters should only be converted to snake case in
|
||||
// the last moment
|
||||
this.params = objectToSnakeCase(value)
|
||||
this.prepareFilters()
|
||||
},
|
||||
immediate: true,
|
||||
|
@ -458,15 +462,7 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
let foundDone = false
|
||||
this.params.filter_by.forEach((f, i) => {
|
||||
if (f === 'done') {
|
||||
foundDone = i
|
||||
}
|
||||
})
|
||||
if (foundDone === false) {
|
||||
this.filters.done = true
|
||||
}
|
||||
this.filters.done = this.params.filter_by.some((f) => f === 'done') === false
|
||||
},
|
||||
async prepareRelatedObjectFilter(kind, filterName = null, servicePrefix = null) {
|
||||
if (filterName === null) {
|
||||
|
|
|
@ -86,11 +86,11 @@ export default {
|
|||
cursor: pointer;
|
||||
width: calc((100% - #{($lists-per-row - 1) * 1rem}) / #{$lists-per-row});
|
||||
height: $list-height;
|
||||
background: $white;
|
||||
background: var(--white);
|
||||
margin: 0 $list-spacing $list-spacing 0;
|
||||
padding: 1rem;
|
||||
border-radius: $radius;
|
||||
box-shadow: $shadow-sm;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: box-shadow $transition;
|
||||
|
||||
display: flex;
|
||||
|
@ -98,13 +98,13 @@ export default {
|
|||
flex-wrap: wrap;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow-md;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:focus:not(:active) {
|
||||
box-shadow: $shadow-xs !important;
|
||||
box-shadow: var(--shadow-xs) !important;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $widescreen) {
|
||||
|
@ -158,7 +158,7 @@ export default {
|
|||
font-family: $vikunja-font;
|
||||
font-weight: 400;
|
||||
font-size: 1.5rem;
|
||||
color: $text;
|
||||
color: var(--text);
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
max-height: calc(100% - 2rem); // 1rem padding, 1rem height of the "is archived" badge
|
||||
|
@ -171,7 +171,7 @@ export default {
|
|||
}
|
||||
|
||||
&.has-light-text .title {
|
||||
color: $light;
|
||||
color: var(--light);
|
||||
}
|
||||
|
||||
&.has-background {
|
||||
|
@ -180,8 +180,8 @@ export default {
|
|||
background-position: center;
|
||||
|
||||
.title {
|
||||
text-shadow: 0 0 10px $black, 1px 1px 5px $grey-700, -1px -1px 5px $grey-700;
|
||||
color: $white;
|
||||
text-shadow: 0 0 10px var(--black), 1px 1px 5px var(--grey-700), -1px -1px 5px var(--grey-700);
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,7 +190,7 @@ export default {
|
|||
opacity: 0;
|
||||
|
||||
&:hover {
|
||||
color: $orange;
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
&.is-archived {
|
||||
|
@ -200,7 +200,7 @@ export default {
|
|||
&.is-favorite {
|
||||
display: inline-block;
|
||||
opacity: 1;
|
||||
color: $orange;
|
||||
color: var(--warning);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,8 +40,8 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.is-done {
|
||||
background: $green;
|
||||
color: $white;
|
||||
background: var(--success);
|
||||
color: var(--white);
|
||||
padding: .5rem;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
|
|
|
@ -47,8 +47,7 @@
|
|||
|
||||
<script>
|
||||
import {parseURL} from 'ufo'
|
||||
|
||||
const API_DEFAULT_PORT = 3456
|
||||
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
|
||||
|
||||
export default {
|
||||
name: 'apiConfig',
|
||||
|
@ -68,131 +67,51 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
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: {
|
||||
setApiUrl() {
|
||||
async setApiUrl() {
|
||||
if (this.apiUrl === '') {
|
||||
// Don't try to check and set an empty url
|
||||
this.errorMsg = this.$t('apiConfig.urlRequired')
|
||||
return
|
||||
}
|
||||
|
||||
let urlToCheck = this.apiUrl
|
||||
try {
|
||||
const url = await checkAndSetApiUrl(this.apiUrl)
|
||||
|
||||
// Check if the url has an http prefix
|
||||
if (
|
||||
!urlToCheck.startsWith('http://') &&
|
||||
!urlToCheck.startsWith('https://')
|
||||
) {
|
||||
urlToCheck = `http://${urlToCheck}`
|
||||
if (url === '') {
|
||||
// If the config setter function could not figure out a url
|
||||
throw new Error('URL cannot be empty.')
|
||||
}
|
||||
|
||||
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.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})
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -206,9 +125,9 @@ export default {
|
|||
.api-url-info {
|
||||
font-size: .9rem;
|
||||
text-align: right;
|
||||
|
||||
span.url {
|
||||
border-bottom: 1px dashed $primary;
|
||||
}
|
||||
|
||||
.url {
|
||||
border-bottom: 1px dashed var(--primary);
|
||||
}
|
||||
</style>
|
|
@ -4,7 +4,13 @@
|
|||
<p class="card-header-title">
|
||||
{{ title }}
|
||||
</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">
|
||||
<icon :icon="closeIcon"/>
|
||||
</span>
|
||||
|
@ -36,7 +42,7 @@ export default {
|
|||
},
|
||||
closeIcon: {
|
||||
type: String,
|
||||
default: 'angle-right',
|
||||
default: 'times',
|
||||
},
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
|
@ -57,22 +63,22 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
background-color: $white;
|
||||
background-color: var(--white);
|
||||
border-radius: $radius;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid $grey-200;
|
||||
box-shadow: $shadow-sm;
|
||||
border: 1px solid var(--card-border-color);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
box-shadow: none;
|
||||
border-bottom: 1px solid $grey-200;
|
||||
border-bottom: 1px solid var(--card-border-color);
|
||||
border-radius: $radius $radius 0 0;
|
||||
}
|
||||
|
||||
// FIXME: should maybe be merged somehow with modal
|
||||
:deep(.modal-card-foot) {
|
||||
background-color: $grey-50;
|
||||
background-color: var(--grey-50);
|
||||
border-top: 0;
|
||||
}
|
||||
</style>
|
|
@ -6,7 +6,6 @@
|
|||
:padding="false"
|
||||
class="has-text-left has-overflow"
|
||||
:has-close="true"
|
||||
close-icon="times"
|
||||
@close="$router.back()"
|
||||
: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>
|
77
src/components/misc/keyboard-shortcuts/index.vue
Normal file
77
src/components/misc/keyboard-shortcuts/index.vue
Normal file
|
@ -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>
|
88
src/components/misc/keyboard-shortcuts/shortcuts.js
Normal file
88
src/components/misc/keyboard-shortcuts/shortcuts.js
Normal file
|
@ -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 {
|
||||
margin-top: 1rem;
|
||||
text-align: right;
|
||||
color: $grey-300;
|
||||
color: var(--grey-300);
|
||||
font-size: 1rem;
|
||||
}
|
||||
</style>
|
43
src/components/misc/no-auth-wrapper.vue
Normal file
43
src/components/misc/no-auth-wrapper.vue
Normal file
|
@ -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: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
|
||||
|
|
54
src/components/misc/popup.vue
Normal file
54
src/components/misc/popup.vue
Normal file
|
@ -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>
|
143
src/components/misc/ready.vue
Normal file
143
src/components/misc/ready.vue
Normal file
|
@ -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>
|
||||
<span class="shortcuts">
|
||||
<component :is="is" class="shortcuts">
|
||||
<template v-for="(k, i) in keys" :key="i">
|
||||
<kbd>{{ k }}</kbd>
|
||||
<span v-if="i < keys.length - 1">+</span>
|
||||
<span v-if="i < keys.length - 1">{{ combination }}</span>
|
||||
</template>
|
||||
</span>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -15,6 +15,14 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
combination: {
|
||||
type: String,
|
||||
default: '+',
|
||||
},
|
||||
is: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -27,8 +35,8 @@ export default {
|
|||
|
||||
kbd {
|
||||
padding: .1rem .35rem;
|
||||
border: 1px solid $grey-300;
|
||||
background: $grey-100;
|
||||
border: 1px solid var(--grey-300);
|
||||
background: var(--grey-100);
|
||||
border-radius: 3px;
|
||||
font-size: .75rem;
|
||||
}
|
||||
|
|
|
@ -12,8 +12,7 @@
|
|||
class="modal-container"
|
||||
:class="{'has-overflow': overflow}"
|
||||
@click.self.prevent.stop="$emit('close')"
|
||||
@shortkey="$emit('close')"
|
||||
v-shortkey="['esc']"
|
||||
v-shortcut="'Escape'"
|
||||
>
|
||||
<div
|
||||
class="modal-content"
|
||||
|
|
|
@ -25,15 +25,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
namespaces() {
|
||||
if (this.query === '') {
|
||||
return []
|
||||
}
|
||||
|
||||
return this.$store.state.namespaces.namespaces.filter(n => {
|
||||
return !n.isArchived &&
|
||||
n.id > 0 &&
|
||||
n.title.toLowerCase().includes(this.query.toLowerCase())
|
||||
})
|
||||
return this.$store.getters['namespaces/searchNamespace'](this.query)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -145,9 +145,9 @@ export default {
|
|||
width: .75rem;
|
||||
height: .75rem;
|
||||
|
||||
background: $primary;
|
||||
background: var(--primary);
|
||||
border-radius: 100%;
|
||||
border: 2px solid $white;
|
||||
border: 2px solid var(--white);
|
||||
}
|
||||
|
||||
.notifications-list {
|
||||
|
@ -157,12 +157,12 @@ export default {
|
|||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
|
||||
background: $white;
|
||||
background: var(--white);
|
||||
width: 350px;
|
||||
max-width: calc(100vw - 2rem);
|
||||
padding: .75rem .25rem;
|
||||
border-radius: $radius;
|
||||
box-shadow: $shadow-sm;
|
||||
box-shadow: var(--shadow-sm);
|
||||
font-size: .85rem;
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
|
@ -183,14 +183,14 @@ export default {
|
|||
transition: background-color $transition;
|
||||
|
||||
&:hover {
|
||||
background: $grey-100;
|
||||
background: var(--grey-100);
|
||||
border-radius: $radius;
|
||||
}
|
||||
|
||||
.read-indicator {
|
||||
width: .35rem;
|
||||
height: .35rem;
|
||||
background: $primary;
|
||||
background: var(--primary);
|
||||
border-radius: 100%;
|
||||
margin-left: .5rem;
|
||||
|
||||
|
@ -219,7 +219,7 @@ export default {
|
|||
}
|
||||
|
||||
.created {
|
||||
color: $grey-400;
|
||||
color: var(--grey-400);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
|
@ -227,14 +227,14 @@ export default {
|
|||
}
|
||||
|
||||
a {
|
||||
color: $grey-800;
|
||||
color: var(--grey-800);
|
||||
}
|
||||
}
|
||||
|
||||
.nothing {
|
||||
text-align: center;
|
||||
padding: 1rem 0;
|
||||
color: $grey-500;
|
||||
color: var(--grey-500);
|
||||
|
||||
.explainer {
|
||||
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 ListModel from '@/models/list'
|
||||
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_TASK = 'task'
|
||||
|
@ -107,40 +110,33 @@ export default {
|
|||
results() {
|
||||
let lists = []
|
||||
if (this.searchMode === SEARCH_MODE_ALL || this.searchMode === SEARCH_MODE_LISTS) {
|
||||
let query = this.query
|
||||
if (this.searchMode === SEARCH_MODE_LISTS) {
|
||||
query = query.substr(1)
|
||||
}
|
||||
const {list} = this.parsedQuery
|
||||
|
||||
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)
|
||||
}),
|
||||
...Object.values(this.$store.state.lists)])]
|
||||
...this.$store.getters['lists/searchList'](list),
|
||||
])]
|
||||
|
||||
lists = (allLists.filter(l => {
|
||||
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 !ncache[l.namespaceId].isArchived
|
||||
})
|
||||
}
|
||||
|
||||
return l.title.toLowerCase().includes(query.toLowerCase())
|
||||
}) ?? [])
|
||||
}
|
||||
|
||||
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() {
|
||||
return Object.keys(this.$store.state[CURRENT_LIST]).length === 0 ? null : this.$store.state[CURRENT_LIST]
|
||||
|
@ -236,18 +234,23 @@ export default {
|
|||
|
||||
return cmds
|
||||
},
|
||||
parsedQuery() {
|
||||
return parseTaskText(this.query, getQuickAddMagicMode())
|
||||
},
|
||||
searchMode() {
|
||||
if (this.query === '') {
|
||||
return SEARCH_MODE_ALL
|
||||
}
|
||||
|
||||
if (this.query.startsWith('#')) {
|
||||
const {text, list, labels, assignees} = this.parsedQuery
|
||||
|
||||
if (assignees.length === 0 && text !== '') {
|
||||
return SEARCH_MODE_TASKS
|
||||
}
|
||||
if (this.query.startsWith('*')) {
|
||||
if (assignees.length === 0 && list !== null && text === '' && labels.length === 0) {
|
||||
return SEARCH_MODE_LISTS
|
||||
}
|
||||
if (this.query.startsWith('@')) {
|
||||
if (assignees.length > 0 && list === null && text === '' && labels.length === 0) {
|
||||
return SEARCH_MODE_TEAMS
|
||||
}
|
||||
|
||||
|
@ -268,12 +271,7 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
let query = this.query
|
||||
if (this.searchMode === SEARCH_MODE_TASKS) {
|
||||
query = query.substr(1)
|
||||
}
|
||||
|
||||
if (query === '' || this.selectedCmd !== null) {
|
||||
if (this.selectedCmd !== null) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -282,8 +280,35 @@ export default {
|
|||
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 () => {
|
||||
const r = await this.taskService.getAll({}, {s: query})
|
||||
const r = await this.taskService.getAll({}, params)
|
||||
this.foundTasks = r.map(t => {
|
||||
t.type = TYPE_TASK
|
||||
const list = this.$store.getters['lists/getListById'](t.listId)
|
||||
|
@ -301,12 +326,7 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
let query = this.query
|
||||
if (this.searchMode === SEARCH_MODE_TEAMS) {
|
||||
query = query.substr(1)
|
||||
}
|
||||
|
||||
if (query === '' || this.selectedCmd !== null) {
|
||||
if (this.query === '' || this.selectedCmd !== null) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -315,11 +335,14 @@ export default {
|
|||
this.teamSearchTimeout = null
|
||||
}
|
||||
|
||||
const {assignees} = this.parsedQuery
|
||||
|
||||
this.teamSearchTimeout = setTimeout(async () => {
|
||||
const r = await this.teamService.getAll({}, {s: query})
|
||||
this.foundTeams = r.map(t => {
|
||||
t.title = t.name
|
||||
return t
|
||||
const teamSearchPromises = assignees.map((t) => this.teamService.getAll({}, {s: t}))
|
||||
const teamsResult = await Promise.all(teamSearchPromises)
|
||||
this.foundTeams = teamsResult.flatMap(team => {
|
||||
team.title = team.name
|
||||
return team
|
||||
})
|
||||
}, 150)
|
||||
},
|
||||
|
@ -484,17 +507,19 @@ export default {
|
|||
.active-cmd {
|
||||
font-size: 1.25rem;
|
||||
margin-left: .5rem;
|
||||
background-color: var(--grey-100);
|
||||
color: var(--grey-800);
|
||||
}
|
||||
}
|
||||
|
||||
.results {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
color: $grey-800;
|
||||
color: var(--grey-800);
|
||||
|
||||
.result {
|
||||
&-title {
|
||||
background: $grey-50;
|
||||
background: var(--grey-50);
|
||||
padding: .5rem;
|
||||
display: block;
|
||||
font-size: .75rem;
|
||||
|
@ -505,6 +530,7 @@ export default {
|
|||
font-size: .9rem;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
color: var(--grey-800);
|
||||
text-align: left;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
|
@ -516,12 +542,12 @@ export default {
|
|||
cursor: pointer;
|
||||
|
||||
&:focus, &:hover {
|
||||
background: $grey-50;
|
||||
background: var(--grey-50);
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: $grey-100;
|
||||
background: var(--grey-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
<template>
|
||||
<card
|
||||
class="taskedit"
|
||||
:title="$t('list.list.editTask')"
|
||||
@close="$emit('close')"
|
||||
:has-close="true"
|
||||
>
|
||||
<form @submit.prevent="editTaskSubmit()">
|
||||
<div class="field">
|
||||
<label class="label" for="tasktext">{{ $t('task.attributes.title') }}</label>
|
||||
|
@ -66,6 +72,7 @@
|
|||
{{ $t('task.openDetail') }}
|
||||
</router-link>
|
||||
</form>
|
||||
</card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -160,7 +167,7 @@ ul.assingees {
|
|||
|
||||
a {
|
||||
float: right;
|
||||
color: $red;
|
||||
color: var(--danger);
|
||||
transition: all $transition;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@
|
|||
>
|
||||
{{ $t('filters.title') }}
|
||||
</x-button>
|
||||
</div>
|
||||
<filter-popup
|
||||
:visible="showTaskFilter"
|
||||
v-model="params"
|
||||
@update:modelValue="loadTasks()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dates">
|
||||
<template v-for="(y, yk) in days" :key="yk + 'year'">
|
||||
<div class="months">
|
||||
|
@ -167,15 +167,13 @@
|
|||
</x-button>
|
||||
</form>
|
||||
<transition name="fade">
|
||||
<card
|
||||
<edit-task
|
||||
v-if="isTaskEdit"
|
||||
class="taskedit"
|
||||
:title="$t('list.list.editTask')"
|
||||
@close="() => {isTaskEdit = false;taskToEdit = null}"
|
||||
:has-close="true"
|
||||
>
|
||||
<edit-task :task="taskToEdit"/>
|
||||
</card>
|
||||
:task="taskToEdit"
|
||||
/>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -447,12 +445,12 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$gantt-border: 1px solid $grey-200;
|
||||
$gantt-vertical-border-color: $grey-100;
|
||||
$gantt-border: 1px solid var(--grey-200);
|
||||
$gantt-vertical-border-color: var(--grey-100);
|
||||
|
||||
.gantt-chart {
|
||||
overflow-x: auto;
|
||||
border-top: 1px solid $grey-200;
|
||||
border-top: 1px solid var(--grey-200);
|
||||
|
||||
.dates {
|
||||
display: flex;
|
||||
|
@ -479,8 +477,8 @@ $gantt-vertical-border-color: $grey-100;
|
|||
font-weight: normal;
|
||||
|
||||
&.today {
|
||||
background: $primary;
|
||||
color: $white;
|
||||
background: var(--primary);
|
||||
color: var(--white);
|
||||
border-radius: 5px 5px 0 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@ -502,7 +500,6 @@ $gantt-vertical-border-color: $grey-100;
|
|||
|
||||
.tasks {
|
||||
max-width: unset !important;
|
||||
margin: 0;
|
||||
border-top: $gantt-border;
|
||||
|
||||
.row {
|
||||
|
@ -510,7 +507,7 @@ $gantt-vertical-border-color: $grey-100;
|
|||
|
||||
.task {
|
||||
display: inline-block;
|
||||
border: 2px solid $primary;
|
||||
border: 2px solid var(--primary);
|
||||
font-size: 0.85rem;
|
||||
margin: 0.5rem;
|
||||
border-radius: 6px;
|
||||
|
@ -523,30 +520,30 @@ $gantt-vertical-border-color: $grey-100;
|
|||
user-select: none; // Non-prefixed version
|
||||
|
||||
&.is-current-edit {
|
||||
border-color: $orange !important;
|
||||
border-color: var(--warning) !important;
|
||||
}
|
||||
|
||||
&.has-light-text {
|
||||
color: $light;
|
||||
color: var(--light);
|
||||
|
||||
&.done span:after {
|
||||
border-top: 1px solid $light;
|
||||
border-top: 1px solid var(--light);
|
||||
}
|
||||
|
||||
.edit-toggle {
|
||||
color: $light;
|
||||
color: var(--light);
|
||||
}
|
||||
}
|
||||
|
||||
&.has-dark-text {
|
||||
color: $text;
|
||||
color: var(--text);
|
||||
|
||||
&.done span:after {
|
||||
border-top: 1px solid $dark;
|
||||
border-top: 1px solid var(--dark);
|
||||
}
|
||||
|
||||
.edit-toggle {
|
||||
color: $text;
|
||||
color: var(--text);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -599,8 +596,8 @@ $gantt-vertical-border-color: $grey-100;
|
|||
}
|
||||
|
||||
&.nodate {
|
||||
border: 2px dashed $grey-300;
|
||||
background: $grey-100;
|
||||
border: 2px dashed var(--grey-300);
|
||||
background: var(--grey-100);
|
||||
}
|
||||
|
||||
&:active {
|
||||
|
@ -612,7 +609,6 @@ $gantt-vertical-border-color: $grey-100;
|
|||
|
||||
.taskedit {
|
||||
position: fixed;
|
||||
min-height: 0;
|
||||
top: 10vh;
|
||||
right: 10vw;
|
||||
z-index: 5;
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import TaskCollectionService from '@/services/taskCollection'
|
||||
|
||||
// FIXME: merge with DEFAULT_PARAMS in filters.vue
|
||||
const DEFAULT_PARAMS = {
|
||||
export const getDefaultParams = () => ({
|
||||
sort_by: ['position', 'id'],
|
||||
order_by: ['asc', 'desc'],
|
||||
filter_by: ['done'],
|
||||
filter_value: ['false'],
|
||||
filter_comparator: ['equals'],
|
||||
filter_concat: 'and',
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* This mixin provides a base set of methods and properties to get tasks on a list.
|
||||
|
@ -26,7 +26,7 @@ export default {
|
|||
searchTerm: '',
|
||||
|
||||
showTaskFilter: false,
|
||||
params: DEFAULT_PARAMS,
|
||||
params: {...getDefaultParams()},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
|
@ -267,17 +267,17 @@ export default {
|
|||
padding: .5rem;
|
||||
|
||||
&:hover {
|
||||
background-color: $grey-200;
|
||||
background-color: var(--grey-200);
|
||||
}
|
||||
|
||||
.filename {
|
||||
font-weight: bold;
|
||||
margin-bottom: .25rem;
|
||||
color: $text;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.info {
|
||||
color: $grey-500;
|
||||
color: var(--grey-500);
|
||||
font-size: .9rem;
|
||||
|
||||
p {
|
||||
|
@ -339,17 +339,17 @@ export default {
|
|||
width: 100%;
|
||||
font-size: 5rem;
|
||||
height: auto;
|
||||
text-shadow: $shadow-md;
|
||||
text-shadow: var(--shadow-md);
|
||||
animation: bounce 2s infinite;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin: .5rem auto 2rem;
|
||||
border-radius: 2px;
|
||||
box-shadow: $shadow-md;
|
||||
background: $primary;
|
||||
box-shadow: var(--shadow-md);
|
||||
background: var(--primary);
|
||||
padding: 1rem;
|
||||
color: $white;
|
||||
color: var(--white);
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ export default {
|
|||
|
||||
<style scoped lang="scss">
|
||||
.checklist-summary {
|
||||
color: $grey-500;
|
||||
color: var(--grey-500);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
|
@ -49,10 +49,10 @@ export default {
|
|||
margin-right: .25rem;
|
||||
|
||||
circle {
|
||||
stroke: $grey-400;
|
||||
stroke: var(--grey-400);
|
||||
|
||||
&:last-child {
|
||||
stroke: $primary;
|
||||
stroke: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -276,7 +276,7 @@ export default {
|
|||
|
||||
this.commentEdit.taskId = this.taskId
|
||||
try {
|
||||
const comment = this.taskCommentService.update(this.commentEdit)
|
||||
const comment = await this.taskCommentService.update(this.commentEdit)
|
||||
for (const c in this.comments) {
|
||||
if (this.comments[c].id === this.commentEdit.id) {
|
||||
this.comments[c] = comment
|
||||
|
@ -306,9 +306,6 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.media.comment {
|
||||
align-items: center;
|
||||
|
||||
.media-left {
|
||||
margin: 0 1rem;
|
||||
}
|
||||
|
@ -316,14 +313,9 @@ export default {
|
|||
.comment-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
* {
|
||||
padding-right: .5rem;
|
||||
}
|
||||
gap: .5rem;
|
||||
|
||||
img {
|
||||
display: none;
|
||||
|
||||
@media screen and (max-width: $tablet) {
|
||||
display: block;
|
||||
width: 20px;
|
||||
|
@ -331,6 +323,10 @@ export default {
|
|||
padding-right: 0;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $tablet) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -340,12 +336,6 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.editor {
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.media-content {
|
||||
width: calc(100% - 48px - 2rem);
|
||||
}
|
||||
|
|
|
@ -141,14 +141,14 @@ $defer-task-max-width: 350px + 100px;
|
|||
width: 100%;
|
||||
max-width: $defer-task-max-width;
|
||||
border-radius: $radius;
|
||||
border: 1px solid $grey-200;
|
||||
border: 1px solid var(--grey-200);
|
||||
padding: 1rem;
|
||||
margin: 1rem;
|
||||
background: $white;
|
||||
color: $text;
|
||||
background: var(--white);
|
||||
color: var(--text);
|
||||
cursor: default;
|
||||
z-index: 10;
|
||||
box-shadow: $shadow-lg;
|
||||
box-shadow: var(--shadow-lg);
|
||||
|
||||
@media screen and (max-width: ($defer-task-max-width)) {
|
||||
left: .5rem;
|
||||
|
|
|
@ -127,7 +127,7 @@ export default {
|
|||
}
|
||||
|
||||
:deep(.user img) {
|
||||
border: 2px solid $white;
|
||||
border: 2px solid var(--white);
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
|
@ -135,8 +135,8 @@ export default {
|
|||
position: absolute;
|
||||
top: 4px;
|
||||
left: 2px;
|
||||
color: $red;
|
||||
background: $white;
|
||||
color: var(--danger);
|
||||
background: var(--white);
|
||||
padding: 0 4px;
|
||||
display: block;
|
||||
border-radius: 100%;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
:class="{'disabled': !canWrite}"
|
||||
@blur="save($event.target.textContent)"
|
||||
@keydown.enter.prevent.stop="$event.target.blur()"
|
||||
:contenteditable="canWrite ? 'true' : 'false'"
|
||||
:contenteditable="canWrite ? true : undefined"
|
||||
:spellcheck="false"
|
||||
>
|
||||
{{ task.title.trim() }}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div
|
||||
class="task loader-container draggable"
|
||||
:class="{
|
||||
'is-loading': loadingInternal || loading,
|
||||
'draggable': !(loadingInternal || loading),
|
||||
|
@ -9,7 +10,6 @@
|
|||
@click.ctrl="() => toggleTaskDone(task)"
|
||||
@click.exact="() => $router.push({ name: 'task.kanban.detail', params: { id: task.id } })"
|
||||
@click.meta="() => toggleTaskDone(task)"
|
||||
class="task loader-container draggable"
|
||||
>
|
||||
<span class="task-id">
|
||||
<Done class="kanban-card__done" :is-done="task.done" variant="small" />
|
||||
|
@ -117,13 +117,13 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$task-background: $white;
|
||||
$task-background: var(--white);
|
||||
|
||||
.task {
|
||||
-webkit-touch-callout: none; // iOS Safari
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
box-shadow: $shadow-xs;
|
||||
box-shadow: var(--shadow-xs);
|
||||
display: block;
|
||||
border: 3px solid transparent;
|
||||
|
||||
|
@ -163,7 +163,7 @@ $task-background: $white;
|
|||
}
|
||||
|
||||
&.overdue {
|
||||
color: $red;
|
||||
color: var(--danger);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -219,7 +219,7 @@ $task-background: $white;
|
|||
.footer .icon,
|
||||
.due-date,
|
||||
.priority-label {
|
||||
background: $grey-100;
|
||||
background: var(--grey-100);
|
||||
border-radius: $radius;
|
||||
padding: 0 .5rem;
|
||||
}
|
||||
|
@ -229,7 +229,7 @@ $task-background: $white;
|
|||
}
|
||||
|
||||
.task-id {
|
||||
color: $grey-500;
|
||||
color: var(--grey-500);
|
||||
font-size: .8rem;
|
||||
margin-bottom: .25rem;
|
||||
display: flex;
|
||||
|
@ -244,21 +244,21 @@ $task-background: $white;
|
|||
}
|
||||
|
||||
&.has-light-text {
|
||||
color: $white;
|
||||
color: var(--white);
|
||||
|
||||
.task-id {
|
||||
color: $grey-200;
|
||||
color: var(--grey-200);
|
||||
}
|
||||
|
||||
.footer .icon,
|
||||
.due-date,
|
||||
.priority-label {
|
||||
background: $grey-800;
|
||||
background: var(--grey-800);
|
||||
}
|
||||
|
||||
.footer {
|
||||
.icon svg {
|
||||
fill: $white;
|
||||
fill: var(--white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<template>
|
||||
<multiselect
|
||||
class="control is-expanded"
|
||||
:loading="listSerivce.loading"
|
||||
:placeholder="$t('list.search')"
|
||||
@search="findLists"
|
||||
:search-results="foundLists"
|
||||
|
@ -18,7 +17,6 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import ListService from '../../../services/list'
|
||||
import ListModel from '../../../models/list'
|
||||
import Multiselect from '@/components/input/multiselect.vue'
|
||||
|
||||
|
@ -26,7 +24,6 @@ export default {
|
|||
name: 'listSearch',
|
||||
data() {
|
||||
return {
|
||||
listSerivce: new ListService(),
|
||||
list: new ListModel(),
|
||||
foundLists: [],
|
||||
}
|
||||
|
@ -50,17 +47,8 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
async findLists(query) {
|
||||
if (query === '') {
|
||||
this.clearAll()
|
||||
return
|
||||
}
|
||||
|
||||
this.foundLists = await this.listSerivce.getAll({}, {s: query})
|
||||
},
|
||||
|
||||
clearAll() {
|
||||
this.foundLists = []
|
||||
findLists(query) {
|
||||
this.foundLists = this.$store.getters['lists/searchList'](query)
|
||||
},
|
||||
|
||||
select(list) {
|
||||
|
@ -82,6 +70,6 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.list-namespace-title {
|
||||
color: $grey-500;
|
||||
color: var(--grey-500);
|
||||
}
|
||||
</style>
|
|
@ -54,7 +54,7 @@ export default {
|
|||
}
|
||||
|
||||
span.high-priority {
|
||||
color: $red;
|
||||
color: var(--danger);
|
||||
width: auto !important; // To override the width set in tasks
|
||||
|
||||
.icon {
|
||||
|
@ -64,7 +64,7 @@ span.high-priority {
|
|||
}
|
||||
|
||||
&.not-so-high {
|
||||
color: $orange;
|
||||
color: var(--warning);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -11,7 +11,7 @@
|
|||
:overflow="true"
|
||||
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>
|
||||
|
||||
<h3>{{ $t('task.attributes.labels') }}</h3>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
:placeholder="$t('task.relation.searchPlaceholder')"
|
||||
@search="findTasks"
|
||||
:loading="taskService.loading"
|
||||
:search-results="foundTasks"
|
||||
:search-results="mappedFoundTasks"
|
||||
label="title"
|
||||
v-model="newTaskRelationTask"
|
||||
:creatable="true"
|
||||
|
@ -41,8 +41,17 @@
|
|||
<span
|
||||
class="different-list"
|
||||
v-if="props.option.listId !== listId"
|
||||
>
|
||||
<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')">
|
||||
{{ $store.getters['lists/getListById'](props.option.listId) === null ? '' : $store.getters['lists/getListById'](props.option.listId).title }} >
|
||||
{{ props.option.differentList }} >
|
||||
</span>
|
||||
</span>
|
||||
{{ props.option.title }}
|
||||
</span>
|
||||
|
@ -70,33 +79,36 @@
|
|||
</template>
|
||||
</transition-group>
|
||||
|
||||
<div :key="kind" class="related-tasks" v-for="(rts, kind ) in relatedTasks">
|
||||
<template v-if="rts.length > 0">
|
||||
<span class="title">{{ relationKindTitle(kind, rts.length) }}</span>
|
||||
<div class="tasks noborder">
|
||||
<div :key="t.id" class="task" v-for="t in rts.filter(t => t)">
|
||||
<router-link :to="{ name: $route.name, params: { id: t.id } }">
|
||||
<span :class="{ 'done': t.done}" class="tasktext">
|
||||
<div :key="rts.kind" class="related-tasks" v-for="rts in mappedRelatedTasks">
|
||||
<span class="title">{{ rts.title }}</span>
|
||||
<div class="tasks">
|
||||
<div :key="t.id" class="task" v-for="t in rts.tasks">
|
||||
<router-link :to="{ name: $route.name, params: { id: t.id } }" :class="{ 'done': t.done}">
|
||||
<span
|
||||
class="different-list"
|
||||
v-if="t.listId !== listId"
|
||||
>
|
||||
<span
|
||||
v-if="t.differentNamespace !== null"
|
||||
v-tooltip="$t('task.relation.differentNamespace')">
|
||||
{{ t.differentNamespace }} >
|
||||
</span>
|
||||
<span
|
||||
v-if="t.differentList !== null"
|
||||
v-tooltip="$t('task.relation.differentList')">
|
||||
{{
|
||||
$store.getters['lists/getListById'](t.listId) === null ? '' : $store.getters['lists/getListById'](t.listId).title
|
||||
}} >
|
||||
{{ t.differentList }} >
|
||||
</span>
|
||||
</span>
|
||||
{{ t.title }}
|
||||
</span>
|
||||
</router-link>
|
||||
<a
|
||||
@click="() => {showDeleteModal = true; relationToDelete = {relationKind: kind, otherTaskId: t.id}}"
|
||||
@click="() => {showDeleteModal = true; relationToDelete = {relationKind: rts.kind, otherTaskId: t.id}}"
|
||||
class="remove"
|
||||
v-if="editEnabled">
|
||||
<icon icon="trash-alt"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<p class="none" v-if="showNoRelationsNotice && Object.keys(relatedTasks).length === 0">
|
||||
{{ $t('task.relation.noneYet') }}
|
||||
|
@ -183,6 +195,19 @@ export default {
|
|||
showCreate() {
|
||||
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: {
|
||||
async findTasks(query) {
|
||||
|
@ -217,15 +242,14 @@ export default {
|
|||
try {
|
||||
await this.taskRelationService.delete(rel)
|
||||
|
||||
Object.entries(this.relatedTasks).some(([relationKind, t]) => {
|
||||
const found = typeof this.relatedTasks[relationKind][t] !== 'undefined' &&
|
||||
this.relatedTasks[relationKind][t].id === this.relationToDelete.otherTaskId &&
|
||||
relationKind === this.relationToDelete.relationKind
|
||||
if (!found) return false
|
||||
const kind = this.relationToDelete.relationKind
|
||||
for (const t in this.relatedTasks[kind]) {
|
||||
if (this.relatedTasks[kind][t].id === this.relationToDelete.otherTaskId) {
|
||||
this.relatedTasks[kind].splice(t, 1)
|
||||
|
||||
this.relatedTasks[relationKind].splice(t, 1)
|
||||
return true
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.saved = true
|
||||
setTimeout(() => {
|
||||
|
@ -245,13 +269,34 @@ export default {
|
|||
relationKindTitle(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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$remove-icon-width: 24px;
|
||||
|
||||
.add-task-relation-button {
|
||||
margin-top: -3rem;
|
||||
|
||||
|
@ -264,71 +309,59 @@ $remove-icon-width: 24px;
|
|||
}
|
||||
}
|
||||
|
||||
.task-relations {
|
||||
&.is-narrow .columns {
|
||||
display: block;
|
||||
|
||||
.column {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.different-list {
|
||||
color: $grey-500;
|
||||
color: var(--grey-500);
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.related-tasks {
|
||||
.title {
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tasks {
|
||||
margin: 0;
|
||||
|
||||
a:not(.remove) {
|
||||
width: calc(100% - #{$remove-icon-width});
|
||||
}
|
||||
|
||||
.task .tasktext {
|
||||
width: calc(100% - .25rem); // Magic .25rem extra space
|
||||
}
|
||||
|
||||
.remove {
|
||||
width: $remove-icon-width;
|
||||
text-align: center;
|
||||
}
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.task {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: .4rem;
|
||||
justify-content: space-between;
|
||||
padding: .75rem;
|
||||
transition: background-color $transition;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border-radius: $radius;
|
||||
border: 2px solid transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--grey-200);
|
||||
}
|
||||
|
||||
a {
|
||||
color: $text;
|
||||
color: var(--text);
|
||||
transition: color ease $transition-duration;
|
||||
|
||||
&:hover {
|
||||
color: $grey-900;
|
||||
color: var(--grey-900);
|
||||
}
|
||||
}
|
||||
|
||||
.remove {
|
||||
color: $red;
|
||||
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>
|
|
@ -112,7 +112,7 @@ export default {
|
|||
align-items: center;
|
||||
|
||||
&.overdue :deep(.datepicker a.show) {
|
||||
color: $red;
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
|
@ -120,7 +120,7 @@ export default {
|
|||
}
|
||||
|
||||
a.remove {
|
||||
color: $red;
|
||||
color: var(--danger);
|
||||
padding-left: .5rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -227,7 +227,7 @@ export default {
|
|||
border: 2px solid transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: $grey-100;
|
||||
background-color: var(--grey-100);
|
||||
}
|
||||
|
||||
.tasktext,
|
||||
|
@ -239,13 +239,13 @@ export default {
|
|||
flex: 1 0 50%;
|
||||
|
||||
.overdue {
|
||||
color: $red;
|
||||
color: var(--danger);
|
||||
}
|
||||
}
|
||||
|
||||
.task-list {
|
||||
width: auto;
|
||||
color: $grey-400;
|
||||
color: var(--grey-400);
|
||||
font-size: .9rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
@ -273,11 +273,11 @@ export default {
|
|||
}
|
||||
|
||||
a {
|
||||
color: $text;
|
||||
color: var(--text);
|
||||
transition: color ease $transition-duration;
|
||||
|
||||
&:hover {
|
||||
color: $grey-900;
|
||||
color: var(--grey-900);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -288,12 +288,12 @@ export default {
|
|||
transition: opacity $transition, color $transition;
|
||||
|
||||
&:hover {
|
||||
color: $orange;
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
&.is-favorite {
|
||||
opacity: 1;
|
||||
color: $orange;
|
||||
color: var(--warning);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,16 +324,16 @@ export default {
|
|||
|
||||
.tasktext.done {
|
||||
text-decoration: line-through;
|
||||
color: $grey-500;
|
||||
color: var(--grey-500);
|
||||
}
|
||||
|
||||
span.parent-tasks {
|
||||
color: $grey-500;
|
||||
color: var(--grey-500);
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.remove {
|
||||
color: $red;
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
|
@ -351,8 +351,8 @@ export default {
|
|||
left: calc(50% - 1rem);
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-left-color: $grey-300;
|
||||
border-bottom-color: $grey-300;
|
||||
border-left-color: var(--grey-300);
|
||||
border-bottom-color: var(--grey-300);
|
||||
}
|
||||
}
|
||||
</style>
|
48
src/composables/useColorScheme.ts
Normal file
48
src/composables/useColorScheme.ts
Normal file
|
@ -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),
|
||||
}
|
||||
})
|
12
src/composables/useTitle.ts
Normal file
12
src/composables/useTitle.ts
Normal file
|
@ -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
|
||||
}
|
23
src/directives/cypress.ts
Normal file
23
src/directives/cypress.ts
Normal file
|
@ -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
|
17
src/directives/shortcut.ts
Normal file
17
src/directives/shortcut.ts
Normal file
|
@ -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()
|
||||
}
|
||||
},
|
||||
}
|
122
src/helpers/checkAndSetApiUrl.ts
Normal file
122
src/helpers/checkAndSetApiUrl.ts
Normal file
|
@ -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)
|
||||
})
|
||||
}
|
10
src/helpers/isAppleDevice.ts
Normal file
10
src/helpers/isAppleDevice.ts
Normal file
|
@ -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 {createNewIndexer} from '../indexes'
|
||||
|
||||
const {add} = createNewIndexer('labels', ['title', 'description'])
|
||||
|
||||
describe('filter labels', () => {
|
||||
const state = {
|
||||
labels: [
|
||||
{id: 1, title: 'label1'},
|
||||
{id: 2, title: 'label2'},
|
||||
{id: 3, title: 'label3'},
|
||||
{id: 4, title: 'label4'},
|
||||
{id: 5, title: 'label5'},
|
||||
{id: 6, title: 'label6'},
|
||||
{id: 7, title: 'label7'},
|
||||
{id: 8, title: 'label8'},
|
||||
{id: 9, title: 'label9'},
|
||||
],
|
||||
labels: {
|
||||
1: {id: 1, title: 'label1'},
|
||||
2: {id: 2, title: 'label2'},
|
||||
3: {id: 3, title: 'label3'},
|
||||
4: {id: 4, title: 'label4'},
|
||||
5: {id: 5, title: 'label5'},
|
||||
6: {id: 6, title: 'label6'},
|
||||
7: {id: 7, title: 'label7'},
|
||||
8: {id: 8, title: 'label8'},
|
||||
9: {id: 9, title: 'label9'},
|
||||
},
|
||||
}
|
||||
|
||||
Object.values(state.labels).forEach(add)
|
||||
|
||||
it('should return an empty array for an empty query', () => {
|
||||
const labels = filterLabelsByQuery(state, [], '')
|
||||
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
interface label {
|
||||
import {createNewIndexer} from '../indexes'
|
||||
|
||||
const {search} = createNewIndexer('labels', ['title', 'description'])
|
||||
|
||||
export interface label {
|
||||
id: number,
|
||||
title: string,
|
||||
}
|
||||
|
||||
interface labelState {
|
||||
labels: label[],
|
||||
labels: {
|
||||
[k: number]: label,
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,17 +21,12 @@ interface labelState {
|
|||
* @returns {Array}
|
||||
*/
|
||||
export function filterLabelsByQuery(state: labelState, labelsToHide: label[], query: string) {
|
||||
if (query === '') {
|
||||
return []
|
||||
}
|
||||
const labelIdsToHide: number[] = labelsToHide.map(({id}) => id)
|
||||
|
||||
const labelQuery = query.toLowerCase()
|
||||
const labelIds = labelsToHide.map(({id}) => id)
|
||||
return Object
|
||||
.values(state.labels)
|
||||
.filter(({id, title}) => {
|
||||
return !labelIds.includes(id) && title.toLowerCase().includes(labelQuery)
|
||||
})
|
||||
return search(query)
|
||||
?.filter(value => !labelIdsToHide.includes(value))
|
||||
.map(id => state.labels[id])
|
||||
|| []
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
export const setTitle = title => {
|
||||
if (typeof title === 'undefined' || title === '') {
|
||||
document.title = 'Vikunja'
|
||||
return
|
||||
}
|
||||
|
||||
document.title = `${title} | Vikunja`
|
||||
export function setTitle(title) {
|
||||
document.title = (typeof title === 'undefined' || title === '')
|
||||
? 'Vikunja'
|
||||
: `${title} | Vikunja`
|
||||
}
|
|
@ -16,6 +16,16 @@
|
|||
"title": "Nenalezeno",
|
||||
"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": {
|
||||
"auth": {
|
||||
"username": "Uživatelské jméno",
|
||||
|
@ -102,6 +112,15 @@
|
|||
"disabled": "Vypnuto",
|
||||
"todoist": "Todoist",
|
||||
"vikunja": "Vikunja"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Color Scheme",
|
||||
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||
"colorScheme": {
|
||||
"light": "Light",
|
||||
"system": "System",
|
||||
"dark": "Dark"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deletion": {
|
||||
|
@ -344,6 +363,7 @@
|
|||
},
|
||||
"filters": {
|
||||
"title": "Filtry",
|
||||
"clear": "Vymazat filtry",
|
||||
"attributes": {
|
||||
"title": "Název",
|
||||
"titlePlaceholder": "Název uloženého filtru přijde sem…",
|
||||
|
@ -449,7 +469,9 @@
|
|||
"saved": "Uloženo!",
|
||||
"default": "Výchozí",
|
||||
"close": "Zavřít",
|
||||
"download": "Stáhnout"
|
||||
"download": "Stáhnout",
|
||||
"showMenu": "Zobrazit nabídku",
|
||||
"hideMenu": "Skrýt nabídku"
|
||||
},
|
||||
"input": {
|
||||
"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!"
|
||||
},
|
||||
"actions": {
|
||||
"assign": "Přiřadit tuto úlohu uživateli",
|
||||
"assign": "Assign to a user",
|
||||
"label": "Přidat štítky",
|
||||
"priority": "Nastavit prioritu",
|
||||
"dueDate": "Nastavit termín",
|
||||
|
@ -643,6 +665,7 @@
|
|||
"searchPlaceholder": "Hledejte nový úkol, který chcete přidat jako související…",
|
||||
"createPlaceholder": "Přidat toto jako nový související úkol",
|
||||
"differentList": "Tento úkol patří do jiného seznamu.",
|
||||
"differentNamespace": "Tento úkol patří do jiného prostoru.",
|
||||
"noneYet": "Zatím žádné vztahy mezi úkoly.",
|
||||
"delete": "Odstranit vztah k úloze",
|
||||
"deleteText1": "Jste si jisti, že chcete odstranit tento vztah úkolu?",
|
||||
|
@ -743,18 +766,27 @@
|
|||
},
|
||||
"keyboardShortcuts": {
|
||||
"title": "Klávesové zkratky",
|
||||
"general": "Obecné",
|
||||
"allPages": "Tyto zkratky fungují na všech stránkách.",
|
||||
"currentPageOnly": "Tyto zkratky fungují pouze na aktuální stránce.",
|
||||
"toggleMenu": "Přepnout nabídku",
|
||||
"quickSearch": "Otevřít vyhledávání / panel rychlých akcí",
|
||||
"then": "potom",
|
||||
"task": {
|
||||
"title": "Stránka úkolů",
|
||||
"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",
|
||||
"dueDate": "Změnit termín tohoto úkolu",
|
||||
"attachment": "Přidat přílohu k tomuto ú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": {
|
||||
|
@ -776,8 +808,9 @@
|
|||
"urlPlaceholder": "např. https://localhost:3456",
|
||||
"change": "změnit",
|
||||
"signInOn": "Přihlaste se ke svému účtu Vikunja na {0}",
|
||||
"error": "Nelze najít nebo použít instalaci Vikunja na \"{domain}\".",
|
||||
"success": "Pomocí instalace 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}\".",
|
||||
"urlRequired": "Je vyžadována adresa URL."
|
||||
},
|
||||
"loadingError": {
|
||||
"failed": "Načítání selhalo, prosím {0}. Pokud chyba přetrvává, {1}.",
|
||||
|
@ -792,7 +825,7 @@
|
|||
"quickActions": {
|
||||
"commands": "Příkazy",
|
||||
"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",
|
||||
"lists": "Seznamy",
|
||||
"teams": "Týmy",
|
||||
|
|
|
@ -16,6 +16,16 @@
|
|||
"title": "Nicht gefunden",
|
||||
"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": {
|
||||
"auth": {
|
||||
"username": "Anmeldename",
|
||||
|
@ -102,6 +112,15 @@
|
|||
"disabled": "Deaktiviert",
|
||||
"todoist": "Todoist",
|
||||
"vikunja": "Vikunja"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Color Scheme",
|
||||
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||
"colorScheme": {
|
||||
"light": "Light",
|
||||
"system": "System",
|
||||
"dark": "Dark"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deletion": {
|
||||
|
@ -344,6 +363,7 @@
|
|||
},
|
||||
"filters": {
|
||||
"title": "Filter",
|
||||
"clear": "Filter zurücksetzen",
|
||||
"attributes": {
|
||||
"title": "Titel",
|
||||
"titlePlaceholder": "Einen gespeicherten Filternamen eingeben …",
|
||||
|
@ -449,7 +469,9 @@
|
|||
"saved": "Gespeichert!",
|
||||
"default": "Standard",
|
||||
"close": "Schließen",
|
||||
"download": "Herunterladen"
|
||||
"download": "Herunterladen",
|
||||
"showMenu": "Menü anzeigen",
|
||||
"hideMenu": "Menü ausblenden"
|
||||
},
|
||||
"input": {
|
||||
"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!"
|
||||
},
|
||||
"actions": {
|
||||
"assign": "Diese Aufgabe jemandem zuweisen",
|
||||
"assign": "Assign to a user",
|
||||
"label": "Label hinzufügen",
|
||||
"priority": "Priorität 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…",
|
||||
"createPlaceholder": "Füge diese Aufgabe als neue Aufgabenbeziehung hinzu",
|
||||
"differentList": "Diese Aufgabe gehört zu einer anderen Liste.",
|
||||
"differentNamespace": "Diese Aufgabe gehört zu einem anderen Namespace.",
|
||||
"noneYet": "Keine Aufgabenbeziehung vorhanden.",
|
||||
"delete": "Aufgabenbeziehung entfernen",
|
||||
"deleteText1": "Willst du diese Aufgabenbeziehung wirklich entfernen?",
|
||||
|
@ -743,18 +766,27 @@
|
|||
},
|
||||
"keyboardShortcuts": {
|
||||
"title": "Tastenkürzel",
|
||||
"general": "Allgemein",
|
||||
"allPages": "Diese Tastenkürzel funktionieren auf allen Seiten.",
|
||||
"currentPageOnly": "Diese Tastenkürzel funktionieren nur auf der aktuellen Seite.",
|
||||
"toggleMenu": "Das Menü umschalten",
|
||||
"quickSearch": "Such-/Schnellaktionsleiste öffnen",
|
||||
"then": "dann",
|
||||
"task": {
|
||||
"title": "Aufgabenseite",
|
||||
"done": "Eine Aufgabe als erledigt markieren",
|
||||
"assign": "Diese Aufgabe jemandem zuweisen",
|
||||
"assign": "Assign to a user",
|
||||
"labels": "Dieser Aufgabe ein Label hinzufügen",
|
||||
"dueDate": "Ändere das Fälligkeitsdatum dieser Aufgabe",
|
||||
"attachment": "Einen Anhang dieser Aufgabe hinzufügen",
|
||||
"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": {
|
||||
|
@ -776,8 +808,9 @@
|
|||
"urlPlaceholder": "z.B. https://localhost:3456",
|
||||
"change": "ändern",
|
||||
"signInOn": "Melde dich bei deinem Vikunja-Account auf {0} an",
|
||||
"error": "Konnte keine Vikunja-Installation unter „{domain}“ finden oder verwenden.",
|
||||
"success": "Verwende die Vikunja-Installation unter „{domain}“."
|
||||
"error": "Konnte keine Vikunja-Installation unter „{domain}“ finden oder verwenden. Bitte probiere eine andere Url.",
|
||||
"success": "Verwende die Vikunja-Installation unter „{domain}“.",
|
||||
"urlRequired": "Eine Url ist erforderlich."
|
||||
},
|
||||
"loadingError": {
|
||||
"failed": "Laden fehlgeschlagen, bitte {0}. Wenn der Fehler weiterhin besteht, {1} bitte.",
|
||||
|
@ -792,7 +825,7 @@
|
|||
"quickActions": {
|
||||
"commands": "Befehle",
|
||||
"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",
|
||||
"lists": "Listen",
|
||||
"teams": "Teams",
|
||||
|
|
|
@ -16,6 +16,16 @@
|
|||
"title": "Nid gfunde",
|
||||
"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": {
|
||||
"auth": {
|
||||
"username": "Benutzernamä",
|
||||
|
@ -102,6 +112,15 @@
|
|||
"disabled": "Deaktiviert",
|
||||
"todoist": "Todoist",
|
||||
"vikunja": "Vikunja"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Color Scheme",
|
||||
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||
"colorScheme": {
|
||||
"light": "Light",
|
||||
"system": "System",
|
||||
"dark": "Dark"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deletion": {
|
||||
|
@ -344,6 +363,7 @@
|
|||
},
|
||||
"filters": {
|
||||
"title": "Filter",
|
||||
"clear": "Filter zurücksetzen",
|
||||
"attributes": {
|
||||
"title": "Titl",
|
||||
"titlePlaceholder": "De Name für de g'speicheret Filter chunt da ahne…",
|
||||
|
@ -449,7 +469,9 @@
|
|||
"saved": "Gspeicheret!",
|
||||
"default": "Standard",
|
||||
"close": "Schlüüse",
|
||||
"download": "Herunterladen"
|
||||
"download": "Herunterladen",
|
||||
"showMenu": "Menü anzeigen",
|
||||
"hideMenu": "Menü ausblenden"
|
||||
},
|
||||
"input": {
|
||||
"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!"
|
||||
},
|
||||
"actions": {
|
||||
"assign": "Die Uufgab emne Benutzer zuewiise",
|
||||
"assign": "Assign to a user",
|
||||
"label": "Label hinzuefüege",
|
||||
"priority": "Priorität setzä",
|
||||
"dueDate": "Fälligkeitsdatum setze",
|
||||
|
@ -643,6 +665,7 @@
|
|||
"searchPlaceholder": "Schriib, um e neui Uufgab als Zueghörigkeit hinzuezfüege…",
|
||||
"createPlaceholder": "Das als en neui Zueghörigkeit hinzuefüege",
|
||||
"differentList": "Die Uufgab ghöört zu ere andere Liste.",
|
||||
"differentNamespace": "Diese Aufgabe gehört zu einem anderen Namespace.",
|
||||
"noneYet": "S'git kei Uufgabe Beziehige.",
|
||||
"delete": "Uufgabe Beziehig chüble",
|
||||
"deleteText1": "Bisch du dir sicher, dass du die Zueghörigkeit chüblä wetsch?",
|
||||
|
@ -743,18 +766,27 @@
|
|||
},
|
||||
"keyboardShortcuts": {
|
||||
"title": "Tastatuurchürzl",
|
||||
"general": "Allgemein",
|
||||
"allPages": "Die Chürzl funktioniered uf allne Siitene.",
|
||||
"currentPageOnly": "Die Chürzl funktioniered nur uf de momentane Siite.",
|
||||
"toggleMenu": "Menü umschalte",
|
||||
"quickSearch": "Suechi und Schnellaktionsliste öffne",
|
||||
"then": "dann",
|
||||
"task": {
|
||||
"title": "Uufgabesiite",
|
||||
"done": "Uufgab als erledigt markiere",
|
||||
"assign": "Die Uufgab emne Benutzer zuewiise",
|
||||
"assign": "Assign to a user",
|
||||
"labels": "Labels ennere Uufgab hinzuefüege",
|
||||
"dueDate": "S'Fälligkeitsdatum für die Uufgab ändere",
|
||||
"attachment": "En Aahang dere Uufgab hinzuefüege",
|
||||
"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": {
|
||||
|
@ -776,8 +808,9 @@
|
|||
"urlPlaceholder": "z.B. https://localhost:3456",
|
||||
"change": "ändere",
|
||||
"signInOn": "Dich i diin Vikunja-Account Iihloge uf {0}",
|
||||
"error": "Es het kei Vikunja Installation uf \"{domain}\".",
|
||||
"success": "Benutze d'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}\".",
|
||||
"urlRequired": "Eine Url ist erforderlich."
|
||||
},
|
||||
"loadingError": {
|
||||
"failed": "Ladä isch fählgschlage, bitte {0}. Wenn de Fähler bestaht, denn {1}.",
|
||||
|
@ -792,7 +825,7 @@
|
|||
"quickActions": {
|
||||
"commands": "Befehl",
|
||||
"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",
|
||||
"lists": "Listene",
|
||||
"teams": "Teams",
|
||||
|
|
|
@ -16,6 +16,16 @@
|
|||
"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",
|
||||
|
@ -102,6 +112,15 @@
|
|||
"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": {
|
||||
|
@ -344,6 +363,7 @@
|
|||
},
|
||||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -449,7 +469,9 @@
|
|||
"saved": "Saved!",
|
||||
"default": "Default",
|
||||
"close": "Close",
|
||||
"download": "Download"
|
||||
"download": "Download",
|
||||
"showMenu": "Show the menu",
|
||||
"hideMenu": "Hide the menu"
|
||||
},
|
||||
"input": {
|
||||
"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!"
|
||||
},
|
||||
"actions": {
|
||||
"assign": "Assign this task to a user",
|
||||
"assign": "Assign to a user",
|
||||
"label": "Add labels",
|
||||
"priority": "Set Priority",
|
||||
"dueDate": "Set Due Date",
|
||||
|
@ -643,6 +665,7 @@
|
|||
"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?",
|
||||
|
@ -743,18 +766,27 @@
|
|||
},
|
||||
"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 this task to a user",
|
||||
"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": {
|
||||
|
@ -776,8 +808,9 @@
|
|||
"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}\".",
|
||||
"success": "Using 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}\".",
|
||||
"urlRequired": "A url is required."
|
||||
},
|
||||
"loadingError": {
|
||||
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||
|
@ -792,7 +825,7 @@
|
|||
"quickActions": {
|
||||
"commands": "Commands",
|
||||
"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",
|
||||
"lists": "Lists",
|
||||
"teams": "Teams",
|
||||
|
|
|
@ -16,6 +16,16 @@
|
|||
"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",
|
||||
|
@ -102,6 +112,15 @@
|
|||
"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": {
|
||||
|
@ -344,6 +363,7 @@
|
|||
},
|
||||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -449,7 +469,9 @@
|
|||
"saved": "Saved!",
|
||||
"default": "Default",
|
||||
"close": "Close",
|
||||
"download": "Download"
|
||||
"download": "Download",
|
||||
"showMenu": "Show the menu",
|
||||
"hideMenu": "Hide the menu"
|
||||
},
|
||||
"input": {
|
||||
"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!"
|
||||
},
|
||||
"actions": {
|
||||
"assign": "Assign this task to a user",
|
||||
"assign": "Assign to a user",
|
||||
"label": "Add labels",
|
||||
"priority": "Set Priority",
|
||||
"dueDate": "Set Due Date",
|
||||
|
@ -643,6 +665,7 @@
|
|||
"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?",
|
||||
|
@ -743,18 +766,27 @@
|
|||
},
|
||||
"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 this task to a user",
|
||||
"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": {
|
||||
|
@ -776,8 +808,9 @@
|
|||
"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}\".",
|
||||
"success": "Using 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}\".",
|
||||
"urlRequired": "A url is required."
|
||||
},
|
||||
"loadingError": {
|
||||
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||
|
@ -792,7 +825,7 @@
|
|||
"quickActions": {
|
||||
"commands": "Commands",
|
||||
"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",
|
||||
"lists": "Lists",
|
||||
"teams": "Teams",
|
||||
|
|
|
@ -16,6 +16,16 @@
|
|||
"title": "Non trouvé",
|
||||
"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": {
|
||||
"auth": {
|
||||
"username": "Nom d’utilisateur·rice",
|
||||
|
@ -102,6 +112,15 @@
|
|||
"disabled": "Désactivé",
|
||||
"todoist": "Todoist",
|
||||
"vikunja": "Vikunja"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Color Scheme",
|
||||
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||
"colorScheme": {
|
||||
"light": "Light",
|
||||
"system": "System",
|
||||
"dark": "Dark"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deletion": {
|
||||
|
@ -344,6 +363,7 @@
|
|||
},
|
||||
"filters": {
|
||||
"title": "Filtres",
|
||||
"clear": "Effacer les filtres",
|
||||
"attributes": {
|
||||
"title": "Nom",
|
||||
"titlePlaceholder": "Entre un nom de filtre enregistré…",
|
||||
|
@ -449,7 +469,9 @@
|
|||
"saved": "Enregistré !",
|
||||
"default": "Par défaut",
|
||||
"close": "Fermer",
|
||||
"download": "Télécharger"
|
||||
"download": "Télécharger",
|
||||
"showMenu": "Afficher le menu",
|
||||
"hideMenu": "Masquer le menu"
|
||||
},
|
||||
"input": {
|
||||
"resetColor": "Réinitialiser la couleur",
|
||||
|
@ -511,7 +533,7 @@
|
|||
"today": "Aujourd’hui",
|
||||
"nextWeek": "La semaine prochaine",
|
||||
"nextMonth": "Le mois prochain",
|
||||
"noTasks": "Nothing to do — Have a nice day!"
|
||||
"noTasks": "Rien à faire — Passe une bonne journée !"
|
||||
},
|
||||
"detail": {
|
||||
"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é !"
|
||||
},
|
||||
"actions": {
|
||||
"assign": "Assigner cette tâche à un·e utilisateur·rice",
|
||||
"assign": "Assign to a user",
|
||||
"label": "Ajouter des étiquettes",
|
||||
"priority": "Définir la priorité",
|
||||
"dueDate": "Définir l’échéance",
|
||||
|
@ -643,6 +665,7 @@
|
|||
"searchPlaceholder": "Écris la recherche d’une nouvelle tâche à ajouter comme connexe…",
|
||||
"createPlaceholder": "Ajouter cette tâche comme nouvelle tâche connexe",
|
||||
"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.",
|
||||
"delete": "Supprimer la relation de tâche",
|
||||
"deleteText1": "Supprimer cette relation de tâche ?",
|
||||
|
@ -743,18 +766,27 @@
|
|||
},
|
||||
"keyboardShortcuts": {
|
||||
"title": "Raccourcis clavier",
|
||||
"general": "Général",
|
||||
"allPages": "Fonctionne sur toutes les pages.",
|
||||
"currentPageOnly": "Fonctionnent uniquement sur la page en cours.",
|
||||
"toggleMenu": "Basculer le menu",
|
||||
"quickSearch": "Ouvrir la barre de recherche/action rapide",
|
||||
"then": "puis",
|
||||
"task": {
|
||||
"title": "Page de tâche",
|
||||
"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",
|
||||
"dueDate": "Modifier la date d’échéance de cette tâche",
|
||||
"attachment": "Ajouter une pièce jointe à 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": {
|
||||
|
@ -776,8 +808,9 @@
|
|||
"urlPlaceholder": "Par exemple : https://localhost:3456",
|
||||
"change": "changer",
|
||||
"signInOn": "Se connecter à ton compte Vikunja sur {0}",
|
||||
"error": "Impossible de trouver ou d’utiliser l’installation Vikunja à « {domain} ».",
|
||||
"success": "Utilisation de 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} ».",
|
||||
"urlRequired": "Une URL est requise."
|
||||
},
|
||||
"loadingError": {
|
||||
"failed": "Le chargement a échoué, {0}. Si l’erreur persiste, {1}.",
|
||||
|
@ -792,7 +825,7 @@
|
|||
"quickActions": {
|
||||
"commands": "Commandes",
|
||||
"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",
|
||||
"lists": "Listes",
|
||||
"teams": "Équipes",
|
||||
|
|
|
@ -16,6 +16,16 @@
|
|||
"title": "Non trovato",
|
||||
"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": {
|
||||
"auth": {
|
||||
"username": "Nome utente",
|
||||
|
@ -102,10 +112,19 @@
|
|||
"disabled": "Disabilitato",
|
||||
"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": "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.",
|
||||
"text2": "Per continuare, inserisci la tua password. Riceverai un'e-mail con ulteriori istruzioni.",
|
||||
"confirm": "Elimina il mio profilo",
|
||||
|
@ -344,6 +363,7 @@
|
|||
},
|
||||
"filters": {
|
||||
"title": "Filtri",
|
||||
"clear": "Clear Filters",
|
||||
"attributes": {
|
||||
"title": "Titolo",
|
||||
"titlePlaceholder": "Il titolo del filtro salvato va qui…",
|
||||
|
@ -449,7 +469,9 @@
|
|||
"saved": "Salvato!",
|
||||
"default": "Predefinito",
|
||||
"close": "Chiudi",
|
||||
"download": "Scarica"
|
||||
"download": "Scarica",
|
||||
"showMenu": "Show the menu",
|
||||
"hideMenu": "Hide the menu"
|
||||
},
|
||||
"input": {
|
||||
"resetColor": "Ripristina Colore",
|
||||
|
@ -506,7 +528,7 @@
|
|||
"titleDates": "Attività dal {from} al {to}",
|
||||
"noDates": "Mostra attività senza date",
|
||||
"current": "Attività attuali",
|
||||
"from": "Attività da",
|
||||
"from": "Tasks from",
|
||||
"until": "until",
|
||||
"today": "Oggi",
|
||||
"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!"
|
||||
},
|
||||
"actions": {
|
||||
"assign": "Assegna questa attività a un utente",
|
||||
"assign": "Assign to a user",
|
||||
"label": "Aggiungi etichette",
|
||||
"priority": "Imposta Priorità",
|
||||
"dueDate": "Imposta data di scadenza",
|
||||
"startDate": "Imposta una data di inizio",
|
||||
"endDate": "Imposta una data di fine",
|
||||
"reminders": "Imposta promemoria",
|
||||
"repeatAfter": "Imposta un intervallo di ripetizione",
|
||||
"repeatAfter": "Set a repeating interval",
|
||||
"percentDone": "Imposta Percentuale Completata",
|
||||
"attachments": "Aggiungi allegati",
|
||||
"relatedTasks": "Aggiungi attività collegate",
|
||||
|
@ -643,6 +665,7 @@
|
|||
"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?",
|
||||
|
@ -743,18 +766,27 @@
|
|||
},
|
||||
"keyboardShortcuts": {
|
||||
"title": "Keyboard Shortcuts",
|
||||
"general": "General",
|
||||
"allPages": "Queste scorciatoie funzionano in tutte le pagine.",
|
||||
"currentPageOnly": "Queste scorciatoie funzionano solo nella pagina attuale.",
|
||||
"toggleMenu": "Attiva/Disattiva Menu",
|
||||
"quickSearch": "Apri la barra di ricerca/azione rapida",
|
||||
"then": "then",
|
||||
"task": {
|
||||
"title": "Task Page",
|
||||
"done": "Mark a task as done",
|
||||
"assign": "Assign this task to a user",
|
||||
"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": {
|
||||
|
@ -776,8 +808,9 @@
|
|||
"urlPlaceholder": "es. http://localhost:8080",
|
||||
"change": "change",
|
||||
"signInOn": "Sign in to your Vikunja account on {0}",
|
||||
"error": "Could not find or use Vikunja installation at \"{domain}\".",
|
||||
"success": "Using 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}\".",
|
||||
"urlRequired": "A url is required."
|
||||
},
|
||||
"loadingError": {
|
||||
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||
|
@ -792,7 +825,7 @@
|
|||
"quickActions": {
|
||||
"commands": "Commands",
|
||||
"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",
|
||||
"lists": "Liste",
|
||||
"teams": "Teams",
|
||||
|
|
|
@ -16,6 +16,16 @@
|
|||
"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",
|
||||
|
@ -102,6 +112,15 @@
|
|||
"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": {
|
||||
|
@ -344,6 +363,7 @@
|
|||
},
|
||||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -449,7 +469,9 @@
|
|||
"saved": "Saved!",
|
||||
"default": "Default",
|
||||
"close": "Close",
|
||||
"download": "Download"
|
||||
"download": "Download",
|
||||
"showMenu": "Show the menu",
|
||||
"hideMenu": "Hide the menu"
|
||||
},
|
||||
"input": {
|
||||
"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!"
|
||||
},
|
||||
"actions": {
|
||||
"assign": "Assign this task to a user",
|
||||
"assign": "Assign to a user",
|
||||
"label": "Add labels",
|
||||
"priority": "Set Priority",
|
||||
"dueDate": "Set Due Date",
|
||||
|
@ -643,6 +665,7 @@
|
|||
"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?",
|
||||
|
@ -743,18 +766,27 @@
|
|||
},
|
||||
"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 this task to a user",
|
||||
"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": {
|
||||
|
@ -776,8 +808,9 @@
|
|||
"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}\".",
|
||||
"success": "Using 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}\".",
|
||||
"urlRequired": "A url is required."
|
||||
},
|
||||
"loadingError": {
|
||||
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||
|
@ -792,7 +825,7 @@
|
|||
"quickActions": {
|
||||
"commands": "Commands",
|
||||
"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",
|
||||
"lists": "Lists",
|
||||
"teams": "Teams",
|
||||
|
|
|
@ -16,6 +16,16 @@
|
|||
"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",
|
||||
|
@ -102,6 +112,15 @@
|
|||
"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": {
|
||||
|
@ -344,6 +363,7 @@
|
|||
},
|
||||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -449,7 +469,9 @@
|
|||
"saved": "Saved!",
|
||||
"default": "Default",
|
||||
"close": "Close",
|
||||
"download": "Download"
|
||||
"download": "Download",
|
||||
"showMenu": "Show the menu",
|
||||
"hideMenu": "Hide the menu"
|
||||
},
|
||||
"input": {
|
||||
"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!"
|
||||
},
|
||||
"actions": {
|
||||
"assign": "Assign this task to a user",
|
||||
"assign": "Assign to a user",
|
||||
"label": "Add labels",
|
||||
"priority": "Set Priority",
|
||||
"dueDate": "Set Due Date",
|
||||
|
@ -643,6 +665,7 @@
|
|||
"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?",
|
||||
|
@ -743,18 +766,27 @@
|
|||
},
|
||||
"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 this task to a user",
|
||||
"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": {
|
||||
|
@ -776,8 +808,9 @@
|
|||
"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}\".",
|
||||
"success": "Using 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}\".",
|
||||
"urlRequired": "A url is required."
|
||||
},
|
||||
"loadingError": {
|
||||
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||
|
@ -792,7 +825,7 @@
|
|||
"quickActions": {
|
||||
"commands": "Commands",
|
||||
"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",
|
||||
"lists": "Lists",
|
||||
"teams": "Teams",
|
||||
|
|
|
@ -16,6 +16,16 @@
|
|||
"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",
|
||||
|
@ -102,6 +112,15 @@
|
|||
"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": {
|
||||
|
@ -344,6 +363,7 @@
|
|||
},
|
||||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -449,7 +469,9 @@
|
|||
"saved": "Saved!",
|
||||
"default": "Default",
|
||||
"close": "Close",
|
||||
"download": "Download"
|
||||
"download": "Download",
|
||||
"showMenu": "Show the menu",
|
||||
"hideMenu": "Hide the menu"
|
||||
},
|
||||
"input": {
|
||||
"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!"
|
||||
},
|
||||
"actions": {
|
||||
"assign": "Assign this task to a user",
|
||||
"assign": "Assign to a user",
|
||||
"label": "Add labels",
|
||||
"priority": "Set Priority",
|
||||
"dueDate": "Set Due Date",
|
||||
|
@ -643,6 +665,7 @@
|
|||
"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?",
|
||||
|
@ -743,18 +766,27 @@
|
|||
},
|
||||
"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 this task to a user",
|
||||
"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": {
|
||||
|
@ -776,8 +808,9 @@
|
|||
"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}\".",
|
||||
"success": "Using 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}\".",
|
||||
"urlRequired": "A url is required."
|
||||
},
|
||||
"loadingError": {
|
||||
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||
|
@ -792,7 +825,7 @@
|
|||
"quickActions": {
|
||||
"commands": "Commands",
|
||||
"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",
|
||||
"lists": "Lists",
|
||||
"teams": "Teams",
|
||||
|
|
|
@ -16,6 +16,16 @@
|
|||
"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",
|
||||
|
@ -102,6 +112,15 @@
|
|||
"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": {
|
||||
|
@ -344,6 +363,7 @@
|
|||
},
|
||||
"filters": {
|
||||
"title": "Filters",
|
||||
"clear": "Clear Filters",
|
||||
"attributes": {
|
||||
"title": "Title",
|
||||
"titlePlaceholder": "The saved filter title goes here…",
|
||||
|
@ -449,7 +469,9 @@
|
|||
"saved": "Saved!",
|
||||
"default": "Default",
|
||||
"close": "Close",
|
||||
"download": "Download"
|
||||
"download": "Download",
|
||||
"showMenu": "Show the menu",
|
||||
"hideMenu": "Hide the menu"
|
||||
},
|
||||
"input": {
|
||||
"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!"
|
||||
},
|
||||
"actions": {
|
||||
"assign": "Assign this task to a user",
|
||||
"assign": "Assign to a user",
|
||||
"label": "Add labels",
|
||||
"priority": "Set Priority",
|
||||
"dueDate": "Set Due Date",
|
||||
|
@ -643,6 +665,7 @@
|
|||
"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?",
|
||||
|
@ -743,18 +766,27 @@
|
|||
},
|
||||
"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 this task to a user",
|
||||
"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": {
|
||||
|
@ -776,8 +808,9 @@
|
|||
"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}\".",
|
||||
"success": "Using 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}\".",
|
||||
"urlRequired": "A url is required."
|
||||
},
|
||||
"loadingError": {
|
||||
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
|
||||
|
@ -792,7 +825,7 @@
|
|||
"quickActions": {
|
||||
"commands": "Commands",
|
||||
"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",
|
||||
"lists": "Lists",
|
||||
"teams": "Teams",
|
||||
|
|
|
@ -16,6 +16,16 @@
|
|||
"title": "Не найдено",
|
||||
"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": {
|
||||
"auth": {
|
||||
"username": "Имя пользователя",
|
||||
|
@ -102,6 +112,15 @@
|
|||
"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": {
|
||||
|
@ -344,6 +363,7 @@
|
|||
},
|
||||
"filters": {
|
||||
"title": "Фильтры",
|
||||
"clear": "Clear Filters",
|
||||
"attributes": {
|
||||
"title": "Название",
|
||||
"titlePlaceholder": "Введи название сохранённого фильтра…",
|
||||
|
@ -449,7 +469,9 @@
|
|||
"saved": "Сохранено!",
|
||||
"default": "По умолчанию",
|
||||
"close": "Закрыть",
|
||||
"download": "Скачать"
|
||||
"download": "Скачать",
|
||||
"showMenu": "Show the menu",
|
||||
"hideMenu": "Hide the menu"
|
||||
},
|
||||
"input": {
|
||||
"resetColor": "Сбросить цвет",
|
||||
|
@ -534,7 +556,7 @@
|
|||
"text2": "Будут удалены все вложения, напоминания и отношения, связанные с этой задачей, и отменить это будет нельзя!"
|
||||
},
|
||||
"actions": {
|
||||
"assign": "Назначить пользователю",
|
||||
"assign": "Assign to a user",
|
||||
"label": "Добавить метки",
|
||||
"priority": "Установить приоритет",
|
||||
"dueDate": "Установить срок",
|
||||
|
@ -643,6 +665,7 @@
|
|||
"searchPlaceholder": "Введи запрос для поиска задачи, чтобы добавить связь…",
|
||||
"createPlaceholder": "Добавить как связанную задачу",
|
||||
"differentList": "Эта задача принадлежит другому списку.",
|
||||
"differentNamespace": "This task belongs to a different namespace.",
|
||||
"noneYet": "Ещё нет связанных задач.",
|
||||
"delete": "Удалить связь",
|
||||
"deleteText1": "Удалить эту связь с задачей?",
|
||||
|
@ -743,18 +766,27 @@
|
|||
},
|
||||
"keyboardShortcuts": {
|
||||
"title": "Сочетания клавиш",
|
||||
"general": "General",
|
||||
"allPages": "Работают на всех страницах.",
|
||||
"currentPageOnly": "Работают только на текущей странице.",
|
||||
"toggleMenu": "Переключить меню",
|
||||
"quickSearch": "Открыть панель поиска/быстрых действий",
|
||||
"then": "then",
|
||||
"task": {
|
||||
"title": "Страница задачи",
|
||||
"done": "Пометить задачу завершённой",
|
||||
"assign": "Назначить задачу пользователю",
|
||||
"assign": "Assign to a user",
|
||||
"labels": "Добавить метки этой задаче",
|
||||
"dueDate": "Изменить срок этой задачи",
|
||||
"attachment": "Добавить вложение к задаче",
|
||||
"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": {
|
||||
|
@ -776,8 +808,9 @@
|
|||
"urlPlaceholder": "напр. https://localhost:3456",
|
||||
"change": "изменить",
|
||||
"signInOn": "Войди в свой аккаунт Vikunja на {0}",
|
||||
"error": "Не удалось найти или использовать Vikunja на \"{domain}\".",
|
||||
"success": "Используется Vikunja на \"{domain}\"."
|
||||
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.",
|
||||
"success": "Используется Vikunja на \"{domain}\".",
|
||||
"urlRequired": "A url is required."
|
||||
},
|
||||
"loadingError": {
|
||||
"failed": "Не удалось загрузить, пожалуйста, {0}. Если ошибка повторится, {1}.",
|
||||
|
@ -792,7 +825,7 @@
|
|||
"quickActions": {
|
||||
"commands": "Команды",
|
||||
"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": "Задачи",
|
||||
"lists": "Списки",
|
||||
"teams": "Команды",
|
||||
|
|
933
src/i18n/lang/tr-TR.json
Normal file
933
src/i18n/lang/tr-TR.json
Normal file
|
@ -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ả",
|
||||
"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": {
|
||||
"auth": {
|
||||
"username": "Tên người dùng",
|
||||
|
@ -102,6 +112,15 @@
|
|||
"disabled": "Vô hiệu hóa",
|
||||
"todoist": "Todoist",
|
||||
"vikunja": "Vikunja"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Color Scheme",
|
||||
"setSuccess": "Saved change of color scheme to {colorScheme}",
|
||||
"colorScheme": {
|
||||
"light": "Light",
|
||||
"system": "System",
|
||||
"dark": "Dark"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deletion": {
|
||||
|
@ -344,6 +363,7 @@
|
|||
},
|
||||
"filters": {
|
||||
"title": "Bộ lọc",
|
||||
"clear": "Xoá các bộ lọc",
|
||||
"attributes": {
|
||||
"title": "Tiêu đề",
|
||||
"titlePlaceholder": "Tiêu đề bộ lọc đã lưu ở đây…",
|
||||
|
@ -449,7 +469,9 @@
|
|||
"saved": "Đã lưu!",
|
||||
"default": "Mặc định",
|
||||
"close": "Đóng",
|
||||
"download": "Tải về"
|
||||
"download": "Tải về",
|
||||
"showMenu": "Hiển thị menu",
|
||||
"hideMenu": "Ẩn menu"
|
||||
},
|
||||
"input": {
|
||||
"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!"
|
||||
},
|
||||
"actions": {
|
||||
"assign": "Chỉ định người đảm nhiệm",
|
||||
"assign": "Assign to a user",
|
||||
"label": "Thêm nhãn",
|
||||
"priority": "Mức độ ưu tiê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…",
|
||||
"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.",
|
||||
"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.",
|
||||
"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?",
|
||||
|
@ -743,18 +766,27 @@
|
|||
},
|
||||
"keyboardShortcuts": {
|
||||
"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.",
|
||||
"currentPageOnly": "Các phím tắt này chỉ hoạt động ở trang hiện tại.",
|
||||
"toggleMenu": "Bật/tắt Menu",
|
||||
"quickSearch": "Mở thanh tìm kiếm/thao tác nhanh",
|
||||
"then": "sau đó",
|
||||
"task": {
|
||||
"title": "Trang công việc",
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
|
@ -776,8 +808,9 @@
|
|||
"urlPlaceholder": "ví dụ: https://localhost:3456",
|
||||
"change": "thay đổi",
|
||||
"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}\".",
|
||||
"success": "Sử dụng cài đặt Vikunja 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}\".",
|
||||
"urlRequired": "Cần có một url."
|
||||
},
|
||||
"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}.",
|
||||
|
@ -792,7 +825,7 @@
|
|||
"quickActions": {
|
||||
"commands": "Các lệnh",
|
||||
"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ụ",
|
||||
"lists": "Danh sách",
|
||||
"teams": "Team",
|
||||
|
|
52
src/indexes/index.ts
Normal file
52
src/indexes/index.ts
Normal file
|
@ -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 {
|
||||
API_URL: string;
|
||||
SENTRY_ENABLED: boolean;
|
||||
SENTRY_DSN: string,
|
||||
SENTRY_DSN: string;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,8 +31,6 @@ import Notifications from '@kyvg/vue3-notification'
|
|||
// PWA
|
||||
import './registerServiceWorker'
|
||||
|
||||
// Shortcuts
|
||||
import shortkey from '@/plugins/shortkey'
|
||||
// Vuex
|
||||
import {store} from './store'
|
||||
// i18n
|
||||
|
@ -55,20 +53,23 @@ const app = createApp(App)
|
|||
|
||||
app.use(Notifications)
|
||||
|
||||
app.use(shortkey, {prevent: ['input', 'textarea', '.input', '[contenteditable]']})
|
||||
|
||||
// directives
|
||||
import focus from './directives/focus'
|
||||
import tooltip from './directives/tooltip'
|
||||
import focus from '@/directives/focus'
|
||||
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('tooltip', tooltip)
|
||||
app.directive('tooltip', VTooltip)
|
||||
app.directive('shortcut', shortcut)
|
||||
app.directive('cy', cypress)
|
||||
|
||||
// global components
|
||||
import FontAwesomeIcon from './icons'
|
||||
import Button from './components/input/button.vue'
|
||||
import Modal from './components/modal/modal.vue'
|
||||
import Card from './components/misc/card.vue'
|
||||
import Button from '@/components/input/button.vue'
|
||||
import Modal from '@/components/modal/modal.vue'
|
||||
import Card from '@/components/misc/card.vue'
|
||||
|
||||
app.component('icon', FontAwesomeIcon)
|
||||
app.component('x-button', Button)
|
||||
|
|
|
@ -7,6 +7,8 @@ import {REPEAT_MODE_DEFAULT} from './constants/taskRepeatModes'
|
|||
import SubscriptionModel from '@/models/subscription'
|
||||
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
||||
|
||||
const SUPPORTS_TRIGGERED_NOTIFICATION = 'Notification' in window && 'showTrigger' in Notification.prototype
|
||||
|
||||
export default class TaskModel extends AbstractModel {
|
||||
|
||||
defaultColor = '198CFF'
|
||||
|
@ -161,7 +163,7 @@ export default class TaskModel extends AbstractModel {
|
|||
}
|
||||
|
||||
async cancelScheduledNotifications() {
|
||||
if (!(Notification && 'showTrigger' in Notification.prototype)) {
|
||||
if (!SUPPORTS_TRIGGERED_NOTIFICATION) {
|
||||
console.debug('This browser does not support triggered notifications')
|
||||
return
|
||||
}
|
||||
|
@ -196,7 +198,7 @@ export default class TaskModel extends AbstractModel {
|
|||
return
|
||||
}
|
||||
|
||||
if (!(Notification && 'showTrigger' in Notification.prototype)) {
|
||||
if (!SUPPORTS_TRIGGERED_NOTIFICATION) {
|
||||
console.debug('This browser does not support triggered notifications')
|
||||
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 ListSettingDelete from '../views/list/settings/delete'
|
||||
import ListSettingArchive from '../views/list/settings/archive'
|
||||
import FilterSettingEdit from '../views/filters/settings/edit'
|
||||
import FilterSettingDelete from '../views/filters/settings/delete'
|
||||
|
||||
// Namespace Settings
|
||||
import NamespaceSettingEdit from '../views/namespaces/settings/edit'
|
||||
import NamespaceSettingShare from '../views/namespaces/settings/share'
|
||||
import NamespaceSettingArchive from '../views/namespaces/settings/archive'
|
||||
import NamespaceSettingDelete from '../views/namespaces/settings/delete'
|
||||
|
||||
// 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 GetPasswordResetComponent = () => import('../views/user/RequestPasswordReset')
|
||||
|
@ -123,6 +125,7 @@ const router = createRouter({
|
|||
path: '/user/settings',
|
||||
name: 'user.settings',
|
||||
component: UserSettingsComponent,
|
||||
redirect: {name: 'user.settings.general'},
|
||||
children: [
|
||||
{
|
||||
path: '/user/settings/avatar',
|
||||
|
@ -279,14 +282,14 @@ const router = createRouter({
|
|||
path: '/lists/:listId/settings/edit',
|
||||
name: 'filter.settings.edit',
|
||||
components: {
|
||||
popup: FilterSettingEdit,
|
||||
popup: FilterEdit,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/settings/delete',
|
||||
name: 'filter.settings.delete',
|
||||
components: {
|
||||
popup: FilterSettingDelete,
|
||||
popup: FilterDelete,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -337,12 +340,12 @@ const router = createRouter({
|
|||
{
|
||||
path: '/lists/:listId/settings/edit',
|
||||
name: 'filter.list.settings.edit',
|
||||
component: FilterSettingEdit,
|
||||
component: FilterEdit,
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/settings/delete',
|
||||
name: 'filter.list.settings.delete',
|
||||
component: FilterSettingDelete,
|
||||
component: FilterDelete,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -389,12 +392,12 @@ const router = createRouter({
|
|||
{
|
||||
path: '/lists/:listId/settings/edit',
|
||||
name: 'filter.gantt.settings.edit',
|
||||
component: FilterSettingEdit,
|
||||
component: FilterEdit,
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/settings/delete',
|
||||
name: 'filter.gantt.settings.delete',
|
||||
component: FilterSettingDelete,
|
||||
component: FilterDelete,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -436,12 +439,12 @@ const router = createRouter({
|
|||
{
|
||||
path: '/lists/:listId/settings/edit',
|
||||
name: 'filter.table.settings.edit',
|
||||
component: FilterSettingEdit,
|
||||
component: FilterEdit,
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/settings/delete',
|
||||
name: 'filter.table.settings.delete',
|
||||
component: FilterSettingDelete,
|
||||
component: FilterDelete,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -488,12 +491,12 @@ const router = createRouter({
|
|||
{
|
||||
path: '/lists/:listId/settings/edit',
|
||||
name: 'filter.kanban.settings.edit',
|
||||
component: FilterSettingEdit,
|
||||
component: FilterEdit,
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/settings/delete',
|
||||
name: 'filter.kanban.settings.delete',
|
||||
component: FilterSettingDelete,
|
||||
component: FilterDelete,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -542,7 +545,7 @@ const router = createRouter({
|
|||
path: '/filters/new',
|
||||
name: 'filters.create',
|
||||
components: {
|
||||
popup: CreateSavedFilter,
|
||||
popup: FilterNew,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -19,6 +19,7 @@ import attachments from './modules/attachments'
|
|||
import labels from './modules/labels'
|
||||
|
||||
import ListService from '../services/list'
|
||||
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
|
||||
|
||||
export const store = createStore({
|
||||
strict: import.meta.env.DEV,
|
||||
|
@ -43,6 +44,7 @@ export const store = createStore({
|
|||
menuActive: true,
|
||||
keyboardShortcutsActive: false,
|
||||
quickActionsActive: false,
|
||||
vikunjaReady: false,
|
||||
},
|
||||
mutations: {
|
||||
[LOADING](state, loading) {
|
||||
|
@ -84,6 +86,9 @@ export const store = createStore({
|
|||
[BACKGROUND](state, background) {
|
||||
state.background = background
|
||||
},
|
||||
vikunjaReady(state, ready) {
|
||||
state.vikunjaReady = ready
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async [CURRENT_LIST]({state, commit}, currentList) {
|
||||
|
@ -138,5 +143,10 @@ export const store = createStore({
|
|||
|
||||
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
Loading…
Reference in New Issue
Block a user