Merge branch 'main' into feature/ready-state

This commit is contained in:
kolaente 2021-11-10 19:42:01 +01:00
commit ce3a53ad17
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
22 changed files with 1013 additions and 478 deletions

View File

@ -21,5 +21,9 @@ indent_size = 2
indent_style = space
indent_size = 2
[*.{scss,css}]
indent_style = space
indent_size = 2
[.nvmrc]
insert_final_newline = false

View File

@ -10,6 +10,7 @@
"build:modern-only": "BUILD_MODERN_ONLY=true vite build && workbox copyLibraries dist/",
"build:dev": "vite build -m development --outDir dist-dev/",
"lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts",
"lint:markup": "vue-tsc --noEmit",
"cypress:open": "cypress open",
"test:unit": "jest",
"test:frontend": "cypress run",
@ -17,9 +18,9 @@
},
"dependencies": {
"@kyvg/vue3-notification": "2.3.4",
"@sentry/tracing": "6.13.3",
"@sentry/vue": "6.13.3",
"@vue/compat": "3.2.20",
"@sentry/tracing": "6.14.1",
"@sentry/vue": "6.14.1",
"@vue/compat": "3.2.21",
"bulma": "0.9.3",
"camel-case": "4.1.2",
"codemirror": "5.63.3",
@ -32,15 +33,15 @@
"is-touch-device": "1.0.1",
"lodash.clonedeep": "4.5.0",
"lodash.debounce": "4.0.8",
"marked": "3.0.8",
"marked": "4.0.0",
"register-service-worker": "1.7.2",
"snake-case": "3.0.4",
"ufo": "0.7.9",
"vue": "3.2.20",
"vue": "3.2.21",
"vue-advanced-cropper": "2.6.3",
"vue-drag-resize": "2.0.3",
"vue-flatpickr-component": "9.0.5",
"vue-i18n": "9.2.0-beta.16",
"vue-i18n": "9.2.0-beta.18",
"vue-router": "4.0.12",
"vuedraggable": "4.1.0",
"vuex": "4.0.2",
@ -53,30 +54,31 @@
"@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.2.0",
"@typescript-eslint/parser": "5.2.0",
"@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.0",
"@vue/eslint-config-typescript": "9.0.1",
"autoprefixer": "10.4.0",
"axios": "0.24.0",
"browserslist": "4.17.5",
"browserslist": "4.17.6",
"cypress": "8.7.0",
"cypress-file-upload": "5.0.8",
"esbuild": "0.13.12",
"eslint": "8.1.0",
"eslint-plugin-vue": "7.20.0",
"esbuild": "0.13.13",
"eslint": "8.2.0",
"eslint-plugin-vue": "8.0.3",
"express": "4.17.1",
"faker": "5.5.3",
"jest": "27.3.1",
"postcss": "8.3.11",
"rollup": "2.58.3",
"rollup": "2.59.0",
"rollup-plugin-visualizer": "5.5.2",
"sass": "1.43.4",
"ts-jest": "27.0.7",
"typescript": "4.4.4",
"vite": "2.6.13",
"vite": "2.6.14",
"vite-plugin-pwa": "0.11.3",
"vue-tsc": "0.29.3",
"wait-on": "6.0.0",
"workbox-cli": "6.3.0"
},
@ -108,7 +110,8 @@
"semi": [
"error",
"never"
]
],
"vue/multi-word-component-names": 0
},
"parser": "vue-eslint-parser",
"parserOptions": {

View File

@ -1,8 +0,0 @@
#!/bin/sh
set -e
# Shell script because yaml doesn't understand the header is a string literal and not a yaml symbol
curl -d operation=pull -H "Authorization: Token $WEBLATE_TOKEN" https://hosted.weblate.org/api/projects/vikunja/repository/
curl -d operation=push -H "Authorization: Token $WEBLATE_TOKEN" https://hosted.weblate.org/api/projects/vikunja/repository/

View File

@ -74,9 +74,9 @@
<namespace-settings-dropdown :namespace="n" v-if="n.id > 0"/>
</div>
<div
v-if="listsVisible[n.id] ?? true"
:key="n.id + 'child'"
class="more-container"
v-if="typeof listsVisible[n.id] !== 'undefined' ? listsVisible[n.id] : true"
>
<!--
NOTE: a v-model / computed setter is not possible, since the updateActiveLists function
@ -134,8 +134,7 @@
:class="{'is-favorite': l.isFavorite}"
@click.prevent.stop="toggleFavoriteList(l)"
class="favorite">
<icon icon="star" v-if="l.isFavorite"/>
<icon :icon="['far', 'star']" v-else/>
<icon :icon="l.isFavorite ? 'star' : ['far', 'star']" />
</span>
</a>
</router-link>
@ -283,22 +282,20 @@ $vikunja-nav-background: $light-background;
$vikunja-nav-color: $grey-700;
$vikunja-nav-selected-width: 0.4rem;
.namespace-container {
background: $vikunja-nav-background;
z-index: 6;
background: $vikunja-nav-background;
color: $vikunja-nav-color;
padding: 0;
transition: all $transition;
padding: 0 0 1rem;
transition: transform $transition-duration ease-in;
position: fixed;
bottom: 0;
top: $navbar-height;
bottom: 0;
left: 0;
transform: translateX(-100%);
overflow-x: auto;
width: $navbar-width;
padding: 0 0 1rem;
left: -147vw;
bottom: 0;
@media screen and (max-width: $tablet) {
top: 0;
@ -306,7 +303,8 @@ $vikunja-nav-selected-width: 0.4rem;
}
&.is-active {
left: 0;
transform: translateX(0);
transition: transform $transition-duration ease-out;
}
.menu {

View File

@ -21,7 +21,14 @@
</svg>
</div>
<x-button :disabled="isEmpty" @click="reset" class="is-small ml-2" :shadow="false" type="secondary">
<x-button
v-if="!isEmpty"
:disabled="isEmpty"
@click="reset"
class="is-small ml-2"
:shadow="false"
type="secondary"
>
{{ $t('input.resetColor') }}
</x-button>
</div>

View File

@ -44,7 +44,7 @@
<script>
import VueEasymde from './vue-easymde/vue-easymde.vue'
import marked from 'marked'
import {marked} from 'marked'
import DOMPurify from 'dompurify'
import hljs from 'highlight.js/lib/common'

View File

@ -7,7 +7,9 @@
@focus="focus"
>
<div class="control" :class="{'is-loading': loading || localLoading}">
<div class="input-wrapper input" :class="{'has-multiple': multiple && Array.isArray(internalValue) && internalValue.length > 0}">
<div
class="input-wrapper input"
:class="{'has-multiple': hasMultiple}">
<template v-if="Array.isArray(internalValue)">
<template v-for="(item, key) in internalValue">
<slot name="tag" :item="item">
@ -81,7 +83,7 @@
</template>
<script>
import { i18n } from '@/i18n'
import {i18n} from '@/i18n'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
export default {
@ -134,9 +136,7 @@ export default {
// If true, will provide an "add this as a new value" entry which fires an @create event when clicking on it.
creatable: {
type: Boolean,
default() {
return false
},
default: false,
},
// The text shown next to the new value option.
createPlaceholder: {
@ -155,23 +155,17 @@ export default {
// If true, allows for selecting multiple items. v-model will be an array with all selected values in that case.
multiple: {
type: Boolean,
default() {
return false
},
default: false,
},
// If true, displays the search results inline instead of using a dropdown.
inline: {
type: Boolean,
default() {
return false
},
default: false,
},
// If true, shows search results when no query is specified.
showEmpty: {
type: Boolean,
default() {
return true
},
default: true,
},
// The delay in ms after which the search event will be fired. Used to avoid hitting the network on every keystroke.
searchDelay: {
@ -180,8 +174,12 @@ export default {
return 200
},
},
closeAfterSelect: {
type: Boolean,
default: true,
},
},
/**
* Available events:
* @search: Triggered every time the search query input changes
@ -234,6 +232,9 @@ export default {
return this.searchResults
},
hasMultiple() {
return this.multiple && Array.isArray(this.internalValue) && this.internalValue.length > 0
},
},
methods: {
// Searching will be triggered with a 200ms delay to avoid searching on every keyup event.
@ -285,7 +286,9 @@ export default {
this.$emit('update:modelValue', this.internalValue)
this.$emit('select', object)
this.setSelectedObject(object)
this.closeSearchResults()
if (this.closeAfterSelect && this.filteredSearchResults.length > 0 && !this.creatableAvailable) {
this.closeSearchResults()
}
},
setSelectedObject(object, resetOnly = false) {
this.internalValue = object

View File

@ -11,7 +11,7 @@
<script>
import EasyMDE from 'easymde'
import marked from 'marked'
import {marked} from 'marked'
export default {
name: 'vue-easymde',

View File

@ -10,7 +10,6 @@
}"
:to="{ name: 'list.index', params: { listId: list.id} }"
class="list-card"
tag="span"
v-if="list !== null && (showArchived ? true : !list.isArchived)"
>
<div class="is-archived-container">

View File

@ -1,7 +1,7 @@
<template>
<div class="notification is-danger">
<i18n-t keypath="loadingError.failed">
<a @click="() => location.reload()">{{ $t('loadingError.tryAgain') }}</a>
<a @click="reload">{{ $t('loadingError.tryAgain') }}</a>
<a href="https://vikunja.io/contact/" rel="noreferrer noopener nofollow" target="_blank">{{ $t('loadingError.contact') }}</a>
</i18n-t>
</div>
@ -10,5 +10,10 @@
<script>
export default {
name: 'error',
methods: {
reload() {
window.location.reload()
},
},
}
</script>

View File

@ -8,15 +8,13 @@
<router-link
:disabled="currentPage === 1 || null"
:to="getRouteForPagination(currentPage - 1)"
class="pagination-previous"
tag="button">
class="pagination-previous">
{{ $t('misc.previous') }}
</router-link>
<router-link
:disabled="currentPage === totalPages || null"
:to="getRouteForPagination(currentPage + 1)"
class="pagination-next"
tag="button">
class="pagination-next">
{{ $t('misc.next') }}
</router-link>
<ul class="pagination-list">

View File

@ -6,11 +6,14 @@
:disabled="taskService.loading || null"
class="input"
:placeholder="$t('list.list.addPlaceholder')"
type="text"
cols="1"
v-focus
v-model="newTaskTitle"
ref="newTaskInput"
:style="{'height': `calc(${textAreaHeight}px - 2px + 1rem)`}"
:style="{
'minHeight': `${initialTextAreaHeight}px`,
'height': `calc(${textAreaHeight}px - 2px + 1rem)`
}"
@keyup="errorMessage = ''"
@keydown.enter="handleEnter"
/>
@ -70,10 +73,10 @@ export default {
},
watch: {
newTaskTitle(newVal) {
// Calculating the textarea height based on lines of input in it. That is more reliable when removing a
// line from the input.
// Calculating the textarea height based on lines of input in it.
// That is more reliable when removing a line from the input.
const numberOfLines = newVal.split(/\r\n|\r|\n/).length
const fontSize = parseInt(window.getComputedStyle(this.$refs.newTaskInput, null).getPropertyValue('font-size'))
const fontSize = parseFloat(window.getComputedStyle(this.$refs.newTaskInput, null).getPropertyValue('font-size'))
this.textAreaHeight = numberOfLines * fontSize * LINE_HEIGHT + INPUT_BORDER_PX
},
@ -145,4 +148,8 @@ export default {
.input, .textarea {
transition: border-color $transition;
}
.input {
resize: vertical;
}
</style>

View File

@ -12,6 +12,7 @@
:create-placeholder="$t('task.label.createPlaceholder')"
v-model="labels"
:search-delay="10"
:close-after-select="false"
>
<template #tag="props">
<span
@ -148,3 +149,9 @@ export default {
},
}
</script>
<style lang="scss" scoped>
.tag {
margin: .5rem 0 0 .5rem;
}
</style>

View File

@ -58,6 +58,9 @@
<span v-if="task.description" class="icon">
<icon icon="align-left"/>
</span>
<span class="icon" v-if="task.repeatAfter.amount > 0">
<icon icon="history"/>
</span>
</div>
</div>
</template>

View File

@ -30,7 +30,7 @@
{{ task.title }}
</span>
<labels class="labels" :labels="task.labels"/>
<labels class="labels ml-2 mr-1" :labels="task.labels" v-if="task.labels.length > 0"/>
<user
:avatar-size="27"
:is-inline="true"
@ -58,6 +58,9 @@
<span class="list-task-icon" v-if="task.description">
<icon icon="align-left"/>
</span>
<span class="list-task-icon" v-if="task.repeatAfter.amount > 0">
<icon icon="history"/>
</span>
</span>
<checklist-summary :task="task"/>
</router-link>
@ -252,11 +255,6 @@ export default {
flex: 0 0 10px;
}
.labels {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.avatar {
border-radius: 50%;
vertical-align: bottom;

View File

@ -234,7 +234,7 @@
"list": {
"title": "Danh sách",
"add": "Thêm",
"addPlaceholder": "Thêm một công việc mới…",
"addPlaceholder": "Thêm việc cần làm…",
"empty": "Danh sách này đang trống trơn.",
"newTaskCta": "Thêm một công việc mới.",
"editTask": "Chỉnh sửa Công việc"
@ -459,7 +459,7 @@
"nextMonday": "Thứ Hai tới",
"thisWeekend": "Cuối tuần này",
"laterThisWeek": "Cuối tuần này",
"nextWeek": "Tuần kế tiếp",
"nextWeek": "Tuần ti",
"chooseDate": "Chọn một ngày"
},
"editor": {
@ -509,8 +509,8 @@
"from": "Công việc từ",
"until": "cho đến",
"today": "Hôm nay",
"nextWeek": "Tuần kế tiếp",
"nextMonth": "Tháng kế tiếp",
"nextWeek": "Tuần ti",
"nextMonth": "Tháng ti",
"noTasks": "Hôm nay thảnh thơi — Hãy tận hưởng ngày tuyệt vời!"
},
"detail": {

View File

@ -120,12 +120,9 @@ export default {
// Not sure if this is the right place to put the logic in, maybe a seperate js component would be better suited.
async register(ctx, credentials) {
const HTTP = HTTPFactory()
ctx.commit(LOADING, true, {root: true})
try {
await HTTP.post('register', {
username: credentials.username,
email: credentials.email,
password: credentials.password,
})
await HTTP.post('register', credentials)
return ctx.dispatch('login', credentials)
} catch(e) {
if (e.response?.data?.message) {

View File

@ -223,13 +223,12 @@ export default {
const labelAddsToWaitFor = parsedLabels.map(async labelTitle => {
let label = validateLabel(labels, labelTitle)
if (typeof label !== 'undefined') {
return label
if (typeof label === 'undefined') {
// label not found, create it
const labelModel = new LabelModel({title: labelTitle})
label = await dispatch('labels/createLabel', labelModel, {root: true})
}
// label not found, create it
const labelModel = new LabelModel({title: labelTitle})
await dispatch('labels/createLabel', labelModel, {root: true})
return addLabelToTask(task, label)
})

View File

@ -5,14 +5,6 @@
.notifications {
left: 0.5rem !important;
bottom: 1rem !important;
.notification-wrapper .notification {
border-radius: 0;
border-top-width: 0;
border-right-width: 0;
border-bottom-width: 0;
border-left-width: 0.4rem;
}
}
.message .message-body {

View File

@ -7,23 +7,14 @@
</div>
</template>
<script>
<script setup>
import { ref } from 'vue'
import ShowTasks from './ShowTasks'
function getNextWeekDate() {
return new Date((new Date()).getTime() + 7 * 24 * 60 * 60 * 1000)
}
export default {
name: 'ShowTasksInRange',
components: {
ShowTasks,
},
data() {
return {
startDate: new Date(),
endDate: getNextWeekDate(),
}
},
}
const startDate = ref(new Date())
const endDate = ref(getNextWeekDate())
</script>

View File

@ -131,12 +131,10 @@ export default {
}),
methods: {
async submit() {
this.$store.commit(LOADING, true)
this.errorMessage = ''
if (this.credentials.password2 !== this.credentials.password) {
this.errorMessage = this.$t('user.auth.passwordsDontMatch')
this.$store.commit(LOADING, false)
return
}

1272
yarn.lock

File diff suppressed because it is too large Load Diff