Merge branch 'main' of ssh://kolaente.dev:9022/vikunja/frontend into main

This commit is contained in:
Sytone 2021-06-07 09:06:23 -07:00
commit 92727e1c01
31 changed files with 579 additions and 389 deletions

View File

@ -3,7 +3,6 @@ import {UserFactory} from '../../factories/user'
import '../../support/authenticateUser'
import {ListFactory} from '../../factories/list'
import {NamespaceFactory} from '../../factories/namespace'
import {TaskFactory} from '../../factories/task'
describe('Namepaces', () => {
let namespaces
@ -99,4 +98,48 @@ describe('Namepaces', () => {
cy.get('.namespace-container .menu.namespaces-lists')
.should('not.contain', newNamespaces[0].title)
})
it('Should not show archived lists & namespaces if the filter is not checked', () => {
const n = NamespaceFactory.create(1, {
id: 2,
is_archived: true,
}, false)
ListFactory.create(1, {
id: 2,
namespace_id: n[0].id,
}, false)
ListFactory.create(1, {
id: 3,
is_archived: true,
}, false)
// Initial
cy.visit('/namespaces')
cy.get('.namespaces-list .namespace')
.should('not.contain', 'Archived')
// Show archived
cy.get('.namespaces-list .fancycheckbox.show-archived-check label.check span')
.should('be.visible')
.click()
cy.get('.namespaces-list .fancycheckbox.show-archived-check input')
.should('be.checked')
cy.get('.namespaces-list .namespace')
.should('contain', 'Archived')
// Don't show archived
cy.get('.namespaces-list .fancycheckbox.show-archived-check label.check span')
.should('be.visible')
.click()
cy.get('.namespaces-list .fancycheckbox.show-archived-check input')
.should('not.be.checked')
// Second time visiting after unchecking
cy.visit('/namespaces')
cy.get('.namespaces-list .fancycheckbox.show-archived-check input')
.should('not.be.checked')
cy.get('.namespaces-list .namespace')
.should('not.contain', 'Archived')
})
})

View File

@ -21,7 +21,7 @@ export class Factory {
* @param override
* @returns {[]}
*/
static create(count = 1, override = {}) {
static create(count = 1, override = {}, truncate = true) {
const data = []
for (let i = 1; i <= count; i++) {
@ -38,7 +38,7 @@ export class Factory {
data.push(entry)
}
seed(this.table, data)
seed(this.table, data, truncate)
return data
}

View File

@ -8,14 +8,14 @@
* @param table
* @param data
*/
export function seed(table, data = {}) {
if(data === null) {
export function seed(table, data = {}, truncate = true) {
if (data === null) {
data = []
}
cy.request({
method: 'PATCH',
url: `${Cypress.env('API_URL')}/test/${table}`,
url: `${Cypress.env('API_URL')}/test/${table}?truncate=${truncate ? 'true' : 'false'}`,
headers: {
'Authorization': Cypress.env('TEST_SECRET'),
},

View File

@ -19,14 +19,14 @@
"copy-to-clipboard": "3.3.1",
"date-fns": "2.22.1",
"dompurify": "2.2.9",
"highlight.js": "11.0.0",
"highlight.js": "11.0.1",
"lodash": "4.17.21",
"marked": "2.0.7",
"register-service-worker": "1.7.2",
"sass": "1.34.0",
"sass": "1.34.1",
"snake-case": "3.0.4",
"verte": "0.0.12",
"vue": "2.6.13",
"vue": "2.6.14",
"vue-advanced-cropper": "1.6.0",
"vue-drag-resize": "1.5.4",
"vue-easymde": "1.4.0",
@ -48,15 +48,15 @@
"babel-eslint": "10.1.0",
"cypress": "7.4.0",
"cypress-file-upload": "5.0.7",
"eslint": "7.27.0",
"eslint": "7.28.0",
"eslint-plugin-vue": "7.10.0",
"faker": "5.5.3",
"jest": "27.0.3",
"jest": "27.0.4",
"sass-loader": "10.2.0",
"vue-flatpickr-component": "8.1.6",
"vue-notification": "1.3.20",
"vue-router": "3.5.1",
"vue-template-compiler": "2.6.13",
"vue-template-compiler": "2.6.14",
"wait-on": "5.3.0"
},
"eslintConfig": {

View File

@ -55,6 +55,7 @@ export default {
},
created() {
this.renewTokenOnFocus()
this.loadLabels()
},
computed: mapState({
namespaces(state) {
@ -126,6 +127,12 @@ export default {
showKeyboardShortcuts() {
this.$store.commit(KEYBOARD_SHORTCUTS_ACTIVE, true)
},
loadLabels() {
this.$store.dispatch('labels/loadAllLabels')
.catch(e => {
this.error(e, this)
})
},
},
}
</script>

View File

@ -118,6 +118,7 @@ import {calculateDayInterval} from '@/helpers/time/calculateDayInterval'
import {calculateNearestHours} from '@/helpers/time/calculateNearestHours'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {createDateFromString} from '@/helpers/time/createDateFromString'
import {mapState} from 'vuex'
export default {
name: 'datepicker',
@ -127,14 +128,6 @@ export default {
show: false,
changed: false,
flatPickerConfig: {
altFormat: 'j M Y H:i',
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
inline: true,
},
// Since flatpickr dates are strings, we need to convert them to native date objects.
// To make that work, we need a separate variable since flatpickr does not have a change event.
flatPickrDate: null,
@ -172,6 +165,19 @@ export default {
this.updateData()
},
},
computed: mapState({
flatPickerConfig: state => ({
altFormat: 'j M Y H:i',
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
inline: true,
locale: {
firstDayOfWeek: state.auth.settings.weekStart,
},
})
}),
methods: {
setDateValue(newVal) {
if(newVal === null) {

View File

@ -34,11 +34,13 @@
<p class="has-text-centered has-text-grey is-italic" v-if="isPreviewActive && text === '' && emptyText !== ''">
{{ emptyText }}
<a @click="toggleEdit">Edit</a>.
<template v-if="isEditEnabled">
<a @click="toggleEdit">Edit</a>.
</template>
</p>
<ul class="actions">
<template v-if="hasEditBottom">
<template v-if="hasEditBottom && isEditEnabled">
<li>
<a v-if="!isEditActive" @click="toggleEdit">Edit</a>
<a v-else @click="toggleEdit">Done</a>

View File

@ -26,7 +26,7 @@
@keyup="search"
@keyup.enter.exact.prevent="() => createOrSelectOnEnter()"
:placeholder="placeholder"
@keydown.down.exact.prevent="() => preSelect(0, true)"
@keydown.down.exact.prevent="() => preSelect(0)"
ref="searchInput"
@focus="() => showSearchResults = true"
/>
@ -35,27 +35,6 @@
<transition name="fade">
<div class="search-results" :class="{'search-results-inline': inline}" v-if="searchResultsVisible">
<button
v-if="creatableAvailable"
class="is-fullwidth"
ref="result--1"
@keydown.up.prevent="() => preSelect(-2)"
@keydown.down.prevent="() => preSelect(0)"
@keyup.enter.prevent="create"
@click.prevent.stop="create"
>
<span>
<slot name="searchResult" :option="query">
<span class="search-result">
{{ query }}
</span>
</slot>
</span>
<span class="hint-text">
{{ createPlaceholder }}
</span>
</button>
<button
class="is-fullwidth"
v-for="(data, key) in filteredSearchResults"
@ -74,6 +53,27 @@
{{ selectPlaceholder }}
</span>
</button>
<button
v-if="creatableAvailable"
class="is-fullwidth"
:ref="`result-${filteredSearchResults.length}`"
@keydown.up.prevent="() => preSelect(filteredSearchResults.length - 1)"
@keydown.down.prevent="() => preSelect(filteredSearchResults.length + 1)"
@keyup.enter.prevent="create"
@click.prevent.stop="create"
>
<span>
<slot name="searchResult" :option="query">
<span class="search-result">
{{ query }}
</span>
</slot>
</span>
<span class="hint-text">
{{ createPlaceholder }}
</span>
</button>
</div>
</transition>
@ -202,14 +202,14 @@ export default {
},
computed: {
searchResultsVisible() {
if(this.query === '' && !this.showEmpty) {
if (this.query === '' && !this.showEmpty) {
return false
}
return this.showSearchResults && (
(this.filteredSearchResults.length > 0) ||
(this.creatable && this.query !== '')
)
(this.filteredSearchResults.length > 0) ||
(this.creatable && this.query !== '')
)
},
creatableAvailable() {
return this.creatable && this.query !== '' && !this.filteredSearchResults.some(elem => {
@ -295,13 +295,8 @@ export default {
this.query = this.label !== '' ? object[this.label] : object
},
preSelect(index, lookForCreatable = false) {
if (index === 0 && this.creatable && lookForCreatable) {
index = -1
}
if (index < -1) {
preSelect(index) {
if (index < 0) {
this.$refs.searchInput.focus()
return
}

View File

@ -131,7 +131,6 @@
<label class="label">Labels</label>
<div class="control">
<multiselect
:loading="labelService.loading"
placeholder="Type to search for a label..."
@search="findLabels"
:search-results="foundLabels"
@ -202,9 +201,9 @@ import PercentDoneSelect from '@/components/tasks/partials/percentDoneSelect'
import Multiselect from '@/components/input/multiselect'
import UserService from '@/services/user'
import LabelService from '@/services/label'
import ListService from '@/services/list'
import NamespaceService from '@/services/namespace'
import {mapState} from 'vuex'
export default {
name: 'filters',
@ -243,21 +242,12 @@ export default {
list_id: '',
namespace: '',
},
flatPickerConfig: {
altFormat: 'j M Y H:i',
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
mode: 'range',
},
usersService: UserService,
foundusers: [],
users: [],
labelService: LabelService,
foundLabels: [],
labelQuery: '',
labels: [],
listsService: ListService,
@ -271,7 +261,6 @@ export default {
},
created() {
this.usersService = new UserService()
this.labelService = new LabelService()
this.listsService = new ListService()
this.namespaceService = new NamespaceService()
},
@ -291,6 +280,30 @@ export default {
this.prepareFilters()
},
},
computed: {
foundLabels() {
const labels = (Object.values(this.$store.state.labels.labels).filter(l => {
return l.title.toLowerCase().includes(this.labelQuery.toLowerCase())
}) ?? [])
return differenceWith(labels, this.labels, (first, second) => {
return first.id === second.id
})
},
...mapState({
flatPickerConfig: state => ({
altFormat: 'j M Y H:i',
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
mode: 'range',
locale: {
firstDayOfWeek: state.auth.settings.weekStart,
},
}),
}),
},
methods: {
change() {
this.$emit('input', this.params)
@ -560,25 +573,8 @@ export default {
this.$set(this.filters, filterName, ids.join(','))
this.setSingleValueFilter(filterName, filterName, '', 'in')
},
clearLabels() {
this.$set(this, 'foundLabels', [])
},
findLabels(query) {
if (query === '') {
this.clearLabels()
}
this.labelService.getAll({}, {s: query})
.then(response => {
// Filter the results to not include labels already selected
this.$set(this, 'foundLabels', differenceWith(response, this.labels, (first, second) => {
return first.id === second.id
}))
})
.catch(e => {
this.error(e, this)
})
this.labelQuery = query
},
addLabel() {
this.$nextTick(() => {

View File

@ -20,7 +20,7 @@
/>
</div>
<div class="has-text-grey-light p-4" v-if="hintText !== ''">
<div class="help has-text-grey-light p-2" v-if="hintText !== ''">
{{ hintText }}
</div>
@ -72,6 +72,11 @@ const CMD_NEW_LIST = 'newList'
const CMD_NEW_NAMESPACE = 'newNamespace'
const CMD_NEW_TEAM = 'newTeam'
const SEARCH_MODE_ALL = 'all'
const SEARCH_MODE_TASKS = 'tasks'
const SEARCH_MODE_LISTS = 'lists'
const SEARCH_MODE_TEAMS = 'teams'
export default {
name: 'quick-actions',
data() {
@ -84,6 +89,7 @@ export default {
taskService: null,
foundTeams: [],
teamSearchTimeout: null,
teamService: null,
namespaceService: null,
@ -99,9 +105,17 @@ export default {
return active
},
results() {
const lists = (Object.values(this.$store.state.lists).filter(l => {
return l.title.toLowerCase().includes(this.query.toLowerCase())
}) ?? [])
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)
}
lists = (Object.values(this.$store.state.lists).filter(l => {
return l.title.toLowerCase().includes(query.toLowerCase())
}) ?? [])
}
const cmds = this.availableCmds
.filter(a => a.title.toLowerCase().includes(this.query.toLowerCase()))
@ -167,7 +181,7 @@ export default {
}
}
return ''
return 'You can use # to only seach for tasks, * to only search for lists and @ to only search for teams.'
},
currentList() {
return Object.keys(this.$store.state[CURRENT_LIST]).length === 0 ? null : this.$store.state[CURRENT_LIST]
@ -196,6 +210,23 @@ export default {
return cmds
},
searchMode() {
if (this.query === '') {
return SEARCH_MODE_ALL
}
if (this.query.startsWith('#')) {
return SEARCH_MODE_TASKS
}
if (this.query.startsWith('*')) {
return SEARCH_MODE_LISTS
}
if (this.query.startsWith('@')) {
return SEARCH_MODE_TEAMS
}
return SEARCH_MODE_ALL
},
},
created() {
this.taskService = new TaskService()
@ -206,9 +237,20 @@ export default {
methods: {
search() {
this.searchTasks()
this.searchTeams()
},
searchTasks() {
if (this.query === '' || this.selectedCmd !== null) {
if (this.searchMode !== SEARCH_MODE_ALL && this.searchMode !== SEARCH_MODE_TASKS) {
this.foundTasks = []
return
}
let query = this.query
if (this.searchMode === SEARCH_MODE_TASKS) {
query = query.substr(1)
}
if (query === '' || this.selectedCmd !== null) {
return
}
@ -218,7 +260,7 @@ export default {
}
this.taskSearchTimeout = setTimeout(() => {
this.taskService.getAll({}, {s: this.query})
this.taskService.getAll({}, {s: query})
.then(r => {
r = r.map(t => {
t.type = TYPE_TASK
@ -233,6 +275,37 @@ export default {
})
}, 150)
},
searchTeams() {
if (this.searchMode !== SEARCH_MODE_ALL && this.searchMode !== SEARCH_MODE_TEAMS) {
this.foundTeams = []
return
}
let query = this.query
if (this.searchMode === SEARCH_MODE_TEAMS) {
query = query.substr(1)
}
if (query === '' || this.selectedCmd !== null) {
return
}
if (this.teamSearchTimeout !== null) {
clearTimeout(this.teamSearchTimeout)
this.teamSearchTimeout = null
}
this.teamSearchTimeout = setTimeout(() => {
this.teamService.getAll({}, {s: query})
.then(r => {
r = r.map(t => {
t.title = t.name
return t
})
this.$set(this, 'foundTeams', r)
})
}, 150)
},
closeQuickActions() {
this.$store.commit(QUICK_ACTIONS_ACTIVE, false)
},
@ -321,7 +394,6 @@ export default {
.then(r => {
this.$store.commit('namespaces/addNamespace', r)
this.success({message: 'The namespace was successfully created.'}, this)
this.$router.back()
this.closeQuickActions()
})
.catch((e) => {

View File

@ -94,14 +94,6 @@ export default {
newTask: TaskModel,
isTaskEdit: false,
taskEditTask: TaskModel,
flatPickerConfig: {
altFormat: 'j M Y H:i',
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
onOpen: this.updateLastReminderDate,
onClose: this.addReminderDate,
},
}
},
components: {

View File

@ -40,6 +40,7 @@
<script>
import TaskService from '../../../services/task'
import flatPickr from 'vue-flatpickr-component'
import {mapState} from 'vuex'
export default {
name: 'defer-task',
@ -51,15 +52,6 @@ export default {
dueDate: null,
lastValue: null,
changeInterval: null,
flatPickerConfig: {
altFormat: 'j M Y H:i',
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
inline: true,
},
}
},
components: {
@ -102,6 +94,19 @@ export default {
this.lastValue = this.dueDate
},
},
computed: mapState({
flatPickerConfig: state => ({
altFormat: 'j M Y H:i',
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
inline: true,
locale: {
firstDayOfWeek: state.auth.settings.weekStart,
},
})
}),
methods: {
deferDays(days) {
this.dueDate = new Date(this.dueDate)

View File

@ -1,6 +1,6 @@
<template>
<multiselect
:loading="labelService.loading || labelTaskService.loading"
:loading="loading"
placeholder="Type to add a new label..."
:multiple="true"
@search="findLabel"
@ -11,6 +11,7 @@
@create="createAndAddLabel"
create-placeholder="Add this as new label"
v-model="labels"
:search-delay="10"
>
<template v-slot:tag="props">
<span
@ -39,11 +40,11 @@
<script>
import differenceWith from 'lodash/differenceWith'
import LabelService from '../../../services/label'
import LabelModel from '../../../models/label'
import LabelTaskService from '../../../services/labelTask'
import Multiselect from '@/components/input/multiselect'
import {LOADING, LOADING_MODULE} from '@/store/mutation-types'
export default {
name: 'edit-labels',
@ -62,12 +63,10 @@ export default {
},
data() {
return {
labelService: LabelService,
labelTaskService: LabelTaskService,
foundLabels: [],
labelTimeout: null,
labels: [],
searchQuery: '',
query: '',
}
},
components: {
@ -79,38 +78,26 @@ export default {
},
},
created() {
this.labelService = new LabelService()
this.labelTaskService = new LabelTaskService()
this.labels = this.value
},
computed: {
foundLabels() {
const labels = (Object.values(this.$store.state.labels.labels).filter(l => {
return l.title.toLowerCase().includes(this.query.toLowerCase())
}) ?? [])
return differenceWith(labels, this.labels, (first, second) => {
return first.id === second.id
})
},
loading() {
return this.labelTaskService.loading || (this.$store.state[LOADING] && this.$store.state[LOADING_MODULE] === 'labels')
},
},
methods: {
findLabel(query) {
this.searchQuery = query
if (query === '') {
this.clearAllLabels()
return
}
if (this.labelTimeout !== null) {
clearTimeout(this.labelTimeout)
}
// Delay the search 300ms to not send a request on every keystroke
this.labelTimeout = setTimeout(() => {
this.labelService.getAll({}, {s: query})
.then(response => {
this.$set(this, 'foundLabels', differenceWith(response, this.labels, (first, second) => {
return first.id === second.id
}))
this.labelTimeout = null
})
.catch(e => {
this.error(e, this)
})
}, 300)
},
clearAllLabels() {
this.$set(this, 'foundLabels', [])
this.query = query
},
addLabel(label, showNotification = true) {
this.$store.dispatch('tasks/addLabel', {label: label, taskId: this.taskId})
@ -141,8 +128,8 @@ export default {
})
},
createAndAddLabel(title) {
let newLabel = new LabelModel({title: title})
this.labelService.create(newLabel)
const newLabel = new LabelModel({title: title})
this.$store.dispatch('labels/createLabel', newLabel)
.then(r => {
this.addLabel(r, false)
this.labels.push(r)
@ -156,7 +143,3 @@ export default {
},
}
</script>
<style scoped>
</style>

View File

@ -9,6 +9,7 @@ export default class UserSettingsModel extends AbstractModel {
discoverableByName: false,
discoverableByEmail: false,
overdueTasksRemindersEnabled: true,
weekStart: 0,
}
}
}

View File

@ -129,8 +129,7 @@ export default class AbstractService {
* @param route
* @returns object
*/
getRouteReplacements(route) {
let parameters = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}
getRouteReplacements(route, parameters = {}) {
let replace$$1 = {}
let pattern = this.getRouteParameterPattern()
pattern = new RegExp(pattern instanceof RegExp ? pattern.source : pattern, 'g')

View File

@ -17,6 +17,7 @@ import kanban from './modules/kanban'
import tasks from './modules/tasks'
import lists from './modules/lists'
import attachments from './modules/attachments'
import labels from './modules/labels'
import ListService from '../services/list'
import {setTitle} from '@/helpers/setTitle'
@ -32,6 +33,7 @@ export const store = new Vuex.Store({
tasks,
lists,
attachments,
labels,
},
state: {
loading: false,

View File

@ -2,6 +2,13 @@ import {HTTPFactory} from '@/http-common'
import {ERROR_MESSAGE, LOADING} from '../mutation-types'
import UserModel from '../../models/user'
const defaultSettings = settings => {
if (typeof settings.weekStart === 'undefined' || settings.weekStart === '') {
settings.weekStart = 0
}
return settings
}
export default {
namespaced: true,
state: () => ({
@ -20,12 +27,12 @@ export default {
state.avatarUrl = info.getAvatarUrl()
if (info.settings) {
state.settings = info.settings
state.settings = defaultSettings(info.settings)
}
}
},
setUserSettings(state, settings) {
state.settings = settings
state.settings = defaultSettings(settings)
const info = state.info !== null ? state.info : {}
info.name = settings.name
state.info = info

100
src/store/modules/labels.js Normal file
View File

@ -0,0 +1,100 @@
import LabelService from '@/services/label'
import Vue from 'vue'
import {setLoading} from '@/store/helper'
export default {
namespaced: true,
// The state is an object which has the label ids as keys.
state: () => ({
labels: {},
loaded: false,
}),
mutations: {
setLabels(state, labels) {
labels.forEach(l => {
Vue.set(state.labels, l.id, l)
})
},
setLabel(state, label) {
Vue.set(state.labels, label.id, label)
},
removeLabelById(state, label) {
Vue.delete(state.labels, label.id)
},
setLoaded(state, loaded) {
state.loaded = loaded
},
},
actions: {
loadAllLabels(ctx, {forceLoad} = {}) {
if (ctx.state.loaded && !forceLoad) {
return Promise.resolve()
}
const cancel = setLoading(ctx, 'labels')
const labelService = new LabelService()
const getAllLabels = (page = 1) => {
return labelService.getAll({}, {}, page)
.then(labels => {
if (page < labelService.totalPages) {
return getAllLabels(page + 1)
.then(nextLabels => {
return labels.concat(nextLabels)
})
} else {
return labels
}
})
.catch(e => {
return Promise.reject(e)
})
}
return getAllLabels()
.then(r => {
ctx.commit('setLabels', r)
ctx.commit('setLoaded', true)
return Promise.resolve(r)
})
.catch(e => Promise.reject(e))
.finally(() => cancel())
},
deleteLabel(ctx, label) {
const cancel = setLoading(ctx, 'labels')
const labelService = new LabelService()
return labelService.delete(label)
.then(r => {
ctx.commit('removeLabelById', label)
return Promise.resolve(r)
})
.catch(e => Promise.reject(e))
.finally(() => cancel())
},
updateLabel(ctx, label) {
const cancel = setLoading(ctx, 'labels')
const labelService = new LabelService()
return labelService.update(label)
.then(r => {
ctx.commit('setLabel', r)
return Promise.resolve(r)
})
.catch(e => Promise.reject(e))
.finally(() => cancel())
},
createLabel(ctx, label) {
const cancel = setLoading(ctx, 'labels')
const labelService = new LabelService()
return labelService.create(label)
.then(r => {
ctx.commit('setLabel', r)
return Promise.resolve(r)
})
.catch(e => Promise.reject(e))
.finally(() => cancel())
},
},
}

View File

@ -152,23 +152,6 @@ $filter-container-top-link-share-list: -47px;
margin-top: $filter-container-top-default;
}
.is-archived {
$notification-height: 1.25rem + 1.25rem + 1.5rem + 1.5rem;
.filter-container {
margin-top: calc(#{$filter-container-top-default} - #{$notification-height});
}
.link-share-container .gantt-chart-container .filter-container,
.gantt-chart-container .filter-container {
margin-top: calc(#{$filter-container-top-link-share-gantt} - 2rem - #{$notification-height});
}
.link-share-container .list-view .filter-container {
margin-top: calc(#{$filter-container-top-link-share-list} - #{$notification-height});
}
.link-share-container .filter-container {
margin-top: calc(#{$filter-container-top-default} - #{$notification-height});
}
.is-archived .notification.is-warning {
margin-bottom: 1rem;
}

View File

@ -199,6 +199,10 @@
}
}
.link-share-container:not(.has-background) .task-view {
background: transparent;
}
.task-view-container {
padding-bottom: 1rem;

View File

@ -1,5 +1,5 @@
<template>
<div :class="{ 'is-loading': labelService.loading}" class="loader-container">
<div :class="{ 'is-loading': loading}" class="loader-container">
<x-button
:to="{name:'labels.create'}"
class="is-pulled-right"
@ -76,7 +76,7 @@
<div class="field has-addons">
<div class="control is-expanded">
<x-button
:loading="labelService.loading"
:loading="loading"
class="is-fullwidth"
@click="editLabelSubmit()"
>
@ -101,11 +101,11 @@
<script>
import {mapState} from 'vuex'
import LabelService from '../../services/label'
import LabelModel from '../../models/label'
import ColorPicker from '../../components/input/colorPicker'
import LoadingComponent from '../../components/misc/loading'
import ErrorComponent from '../../components/misc/error'
import {LOADING, LOADING_MODULE} from '@/store/mutation-types'
export default {
name: 'ListLabels',
@ -120,15 +120,12 @@ export default {
},
data() {
return {
labelService: LabelService,
labels: [],
labelEditLabel: LabelModel,
isLabelEdit: false,
editorActive: false,
}
},
created() {
this.labelService = new LabelService()
this.labelEditLabel = new LabelModel()
this.loadLabels()
},
@ -137,43 +134,19 @@ export default {
},
computed: mapState({
userInfo: state => state.auth.info,
labels: state => state.labels.labels,
loading: state => state[LOADING] && state[LOADING_MODULE] === 'labels',
}),
methods: {
loadLabels() {
const getAllLabels = (page = 1) => {
return this.labelService.getAll({}, {}, page)
.then(labels => {
if (page < this.labelService.totalPages) {
return getAllLabels(page + 1)
.then(nextLabels => {
return labels.concat(nextLabels)
})
} else {
return labels
}
})
.catch(e => {
return Promise.reject(e)
})
}
getAllLabels()
.then(r => {
this.$set(this, 'labels', r)
})
this.$store.dispatch('labels/loadAllLabels')
.catch(e => {
this.error(e, this)
})
},
deleteLabel(label) {
this.labelService.delete(label)
this.$store.dispatch('labels/deleteLabel', label)
.then(() => {
// Remove the label from the list
for (const l in this.labels) {
if (this.labels[l].id === label.id) {
this.labels.splice(l, 1)
}
}
this.success({message: 'The label was successfully deleted.'}, this)
})
.catch(e => {
@ -181,13 +154,8 @@ export default {
})
},
editLabelSubmit() {
this.labelService.update(this.labelEditLabel)
.then(r => {
for (const l in this.labels) {
if (this.labels[l].id === r.id) {
this.$set(this.labels, l, r)
}
}
this.$store.dispatch('labels/updateLabel', this.labelEditLabel)
.then(() => {
this.success({message: 'The label was successfully updated.'}, this)
})
.catch(e => {

View File

@ -8,10 +8,10 @@
<label class="label" for="labelTitle">Label Title</label>
<div
class="control is-expanded"
:class="{ 'is-loading': labelService.loading }"
:class="{ 'is-loading': loading }"
>
<input
:class="{ disabled: labelService.loading }"
:class="{ disabled: loading }"
class="input"
placeholder="The label title goes here..."
type="text"
@ -28,7 +28,7 @@
<div class="field">
<label class="label">Color</label>
<div class="control">
<color-picker v-model="label.hexColor" />
<color-picker v-model="label.hexColor"/>
</div>
</div>
</create-edit>
@ -36,17 +36,16 @@
<script>
import labelModel from '../../models/label'
import labelService from '../../services/label'
import LabelModel from '../../models/label'
import LabelService from '../../services/label'
import CreateEdit from '@/components/misc/create-edit'
import ColorPicker from '../../components/input/colorPicker'
import {mapState} from 'vuex'
import {LOADING, LOADING_MODULE} from '@/store/mutation-types'
export default {
name: 'NewLabel',
data() {
return {
labelService: labelService,
label: labelModel,
showError: false,
}
@ -56,12 +55,14 @@ export default {
ColorPicker,
},
created() {
this.labelService = new LabelService()
this.label = new LabelModel()
},
mounted() {
this.setTitle('Create a new label')
},
computed: mapState({
loading: state => state[LOADING] && state[LOADING_MODULE] === 'labels',
}),
methods: {
newLabel() {
if (this.label.title === '') {
@ -70,17 +71,13 @@ export default {
}
this.showError = false
this.labelService
.create(this.label)
.then((response) => {
this.$store.dispatch('labels/createLabel', this.label)
.then(r => {
this.$router.push({
name: 'labels.index',
params: { id: response.id },
params: {id: r.id},
})
this.success(
{ message: 'The label was successfully created.' },
this,
)
this.success({message: 'The label was successfully created.'}, this)
})
.catch((e) => {
this.error(e, this)

View File

@ -66,6 +66,7 @@ import GanttChart from '../../../components/tasks/gantt-component'
import flatPickr from 'vue-flatpickr-component'
import Fancycheckbox from '../../../components/input/fancycheckbox'
import {saveListView} from '@/helpers/saveListView'
import {mapState} from 'vuex'
export default {
name: 'Gantt',
@ -85,14 +86,19 @@ export default {
dayWidth: 35,
dateFrom: null,
dateTo: null,
flatPickerConfig: {
altFormat: 'j M Y',
altInput: true,
dateFormat: 'Y-m-d',
enableTime: false,
},
}
},
computed: mapState({
flatPickerConfig: state => ({
altFormat: 'j M Y',
altInput: true,
dateFormat: 'Y-m-d',
enableTime: false,
locale: {
firstDayOfWeek: state.auth.settings.weekStart,
},
})
}),
beforeMount() {
this.dateFrom = new Date((new Date()).setDate((new Date()).getDate() - 15))
this.dateTo = new Date((new Date()).setDate((new Date()).getDate() + 30))

View File

@ -31,7 +31,7 @@
@focusout="() => saveBucketTitle(bucket.id)"
@keydown.enter.prevent.stop="() => saveBucketTitle(bucket.id)"
class="title input"
contenteditable="true"
:contenteditable="canWrite"
spellcheck="false">{{ bucket.title }}</h2>
<span
:class="{'is-max': bucket.tasks.length >= bucket.limit}"

View File

@ -165,7 +165,6 @@
import TaskService from '../../../services/task'
import TaskModel from '../../../models/task'
import LabelTaskService from '../../../services/labelTask'
import LabelService from '../../../services/label'
import EditTask from '../../../components/tasks/edit-task'
import AddTask from '../../../components/tasks/add-task'
@ -189,7 +188,6 @@ export default {
showError: false,
labelTaskService: LabelTaskService,
labelService: LabelService,
ctaVisible: false,
}
@ -204,7 +202,6 @@ export default {
},
created() {
this.taskService = new TaskService()
this.labelService = new LabelService()
this.labelTaskService = new LabelTaskService()
// Save the current list view to local storage

View File

@ -108,7 +108,7 @@ export default {
}
},
created() {
this.showArchived = localStorage.getItem('showArchived') ?? false
this.showArchived = JSON.parse(localStorage.getItem('showArchived')) ?? false
this.loadBackgroundsForLists()
},
mounted() {
@ -147,7 +147,7 @@ export default {
.catch(e => this.error(e, this))
},
saveShowArchivedState() {
localStorage.setItem('showArchived', this.showArchived)
localStorage.setItem('showArchived', JSON.stringify(this.showArchived))
},
},
}

View File

@ -1,7 +1,7 @@
<template>
<div class="is-max-width-desktop show-tasks">
<fancycheckbox
@change="loadPendingTasks"
@change="setDate"
class="is-pulled-right"
v-if="!showAll"
v-model="showNulls"
@ -15,7 +15,7 @@
:class="{ 'disabled': taskService.loading}"
:config="flatPickerConfig"
:disabled="taskService.loading"
@on-close="loadPendingTasks"
@on-close="setDate"
class="input"
v-model="cStartDate"
/>
@ -24,7 +24,7 @@
:class="{ 'disabled': taskService.loading}"
:config="flatPickerConfig"
:disabled="taskService.loading"
@on-close="loadPendingTasks"
@on-close="setDate"
class="input"
v-model="cEndDate"
/>
@ -81,14 +81,6 @@ export default {
cEndDate: null,
showNothingToDo: false,
flatPickerConfig: {
altFormat: 'j M Y H:i',
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
},
}
},
props: {
@ -116,8 +108,29 @@ export default {
},
computed: mapState({
userAuthenticated: state => state.auth.authenticated,
flatPickerConfig: state => ({
altFormat: 'j M Y H:i',
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
locale: {
firstDayOfWeek: state.auth.settings.weekStart,
},
})
}),
methods: {
setDate() {
this.$router.push({
name: this.$route.name,
query: {
from: +new Date(this.cStartDate),
to: +new Date(this.cEndDate),
showOverdue: this.showOverdue,
showNulls: this.showNulls,
},
})
},
loadPendingTasks() {
// Since this route is authentication only, users would get an error message if they access the page unauthenticated.
// Since this component is mounted as the home page before unauthenticated users get redirected
@ -127,8 +140,15 @@ export default {
}
// Make sure all dates are date objects
this.cStartDate = new Date(this.cStartDate)
this.cEndDate = new Date(this.cEndDate)
if (typeof this.$route.query.from !== 'undefined' && typeof this.$route.query.to !== 'undefined') {
this.cStartDate = new Date(Number(this.$route.query.from))
this.cEndDate = new Date(Number(this.$route.query.to))
} else {
this.cStartDate = new Date(this.cStartDate)
this.cEndDate = new Date(this.cEndDate)
}
this.showOverdue = this.$route.query.showOverdue
this.showNulls = this.$route.query.showNulls
if (this.showAll) {
this.setTitle('Current Tasks')
@ -177,9 +197,7 @@ export default {
r.sort((a, b) => {
return a.dueDate === null && b.dueDate === null ? -1 : 1
})
const tasks = r.
filter(t => t.dueDate !== null).
concat(r.filter(t => t.dueDate === null))
const tasks = r.filter(t => t.dueDate !== null).concat(r.filter(t => t.dueDate === null))
this.$set(this, 'tasks', tasks)
this.$store.commit(HAS_TASKS, r.length > 0)
@ -205,21 +223,20 @@ export default {
this.cStartDate = new Date()
this.cEndDate = new Date((new Date()).getTime() + 7 * 24 * 60 * 60 * 1000)
this.showOverdue = false
this.loadPendingTasks()
this.setDate()
},
setDatesToNextMonth() {
this.cStartDate = new Date()
this.cEndDate = new Date((new Date()).setMonth((new Date()).getMonth() + 1))
this.showOverdue = false
this.loadPendingTasks()
this.setDate()
},
showTodaysTasks() {
const d = new Date()
this.cStartDate = new Date()
this.cEndDate = new Date(d.setDate(d.getDate() + 1))
this.showNulls = false
this.showOverdue = true
this.loadPendingTasks()
this.setDate()
},
},
}

View File

@ -17,13 +17,10 @@ export default {
},
data() {
return {
startDate: new Date(this.$route.params.startDateUnix),
endDate: new Date(this.$route.params.endDateUnix),
startDate: null,
endDate: null,
}
},
watch: {
'$route': 'setDatesToNextWeek',
},
created() {
this.setDatesToNextWeek()
},

View File

@ -509,6 +509,9 @@ export default {
this.loadTask()
},
computed: {
currentList() {
return this.$store.state[CURRENT_LIST]
},
parent() {
if (!this.task.listId) {
return {
@ -522,11 +525,11 @@ export default {
}
const list = this.$store.getters['namespaces/getListAndNamespaceById'](this.task.listId)
this.$store.commit(CURRENT_LIST, list.list)
this.$store.commit(CURRENT_LIST, list !== null ? list.list : this.currentList)
return list
},
canWrite() {
return this.task && this.task.maxRight && this.task.maxRight > rights.READ
return typeof this.task !== 'undefined' && typeof this.task.maxRight !== 'undefined' && this.task.maxRight > rights.READ
},
updatedSince() {
return this.formatDateSince(this.task.updated)

View File

@ -132,6 +132,19 @@
Play a sound when marking tasks as done
</label>
</div>
<div class="field">
<label class="is-flex is-align-items-center">
<span>
Week starts on
</span>
<div class="select ml-2">
<select v-model.number="settings.weekStart">
<option value="0">Sunday</option>
<option value="1">Monday</option>
</select>
</div>
</label>
</div>
<x-button
:loading="userSettingsService.loading"

239
yarn.lock
View File

@ -1716,15 +1716,15 @@
debug "^3.1.0"
lodash.once "^4.1.1"
"@eslint/eslintrc@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.1.tgz#442763b88cecbe3ee0ec7ca6d6dd6168550cbf14"
integrity sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==
"@eslint/eslintrc@^0.4.2":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.2.tgz#f63d0ef06f5c0c57d76c4ab5f63d3835c51b0179"
integrity sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==
dependencies:
ajv "^6.12.4"
debug "^4.1.1"
espree "^7.3.0"
globals "^12.1.0"
globals "^13.9.0"
ignore "^4.0.6"
import-fresh "^3.2.1"
js-yaml "^3.13.1"
@ -1843,13 +1843,13 @@
jest-util "^27.0.2"
slash "^3.0.0"
"@jest/core@^27.0.3":
version "27.0.3"
resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.0.3.tgz#b5a38675fa0466450a7fd465f4b226762cb592a2"
integrity sha512-rN8lr/OJ8iApcQUh4khnMaOCVX4oRnLwy2tPW3Vh70y62K8Da8fhkxMUq0xX9VPa4+yWUm0tGc/jUSJi+Jzuwg==
"@jest/core@^27.0.4":
version "27.0.4"
resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.0.4.tgz#679bf9ac07900da2ddbb9667bb1afa8029038f53"
integrity sha512-+dsmV8VUs1h/Szb+rEWk8xBM1fp1I///uFy9nk3wXGvRsF2lBp8EVPmtWc+QFRb3MY2b7u2HbkGF1fzoDzQTLA==
dependencies:
"@jest/console" "^27.0.2"
"@jest/reporters" "^27.0.2"
"@jest/reporters" "^27.0.4"
"@jest/test-result" "^27.0.2"
"@jest/transform" "^27.0.2"
"@jest/types" "^27.0.2"
@ -1860,15 +1860,15 @@
exit "^0.1.2"
graceful-fs "^4.2.4"
jest-changed-files "^27.0.2"
jest-config "^27.0.3"
jest-config "^27.0.4"
jest-haste-map "^27.0.2"
jest-message-util "^27.0.2"
jest-regex-util "^27.0.1"
jest-resolve "^27.0.2"
jest-resolve-dependencies "^27.0.3"
jest-runner "^27.0.3"
jest-runtime "^27.0.3"
jest-snapshot "^27.0.2"
jest-resolve "^27.0.4"
jest-resolve-dependencies "^27.0.4"
jest-runner "^27.0.4"
jest-runtime "^27.0.4"
jest-snapshot "^27.0.4"
jest-util "^27.0.2"
jest-validate "^27.0.2"
jest-watcher "^27.0.2"
@ -1909,10 +1909,10 @@
"@jest/types" "^27.0.2"
expect "^27.0.2"
"@jest/reporters@^27.0.2":
version "27.0.2"
resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.0.2.tgz#ad73835d1cd54da08b0998a70b14446405e8e0d9"
integrity sha512-SVQjew/kafNxSN1my4praGQP+VPVGHsU8zqiEDppLvq6j1lryIjdNb9P+bZSsKeifU4bIoaPnf9Ui0tK9WOpFA==
"@jest/reporters@^27.0.4":
version "27.0.4"
resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.0.4.tgz#95609b1be97afb80d55d8aa3d7c3179c15810e65"
integrity sha512-Xa90Nm3JnV0xCe4M6A10M9WuN9krb+WFKxV1A98Y4ePCw40n++r7uxFUNU7DT1i9Behj7fjrAIju9oU0t1QtCg==
dependencies:
"@bcoe/v8-coverage" "^0.2.3"
"@jest/console" "^27.0.2"
@ -1930,7 +1930,7 @@
istanbul-lib-source-maps "^4.0.0"
istanbul-reports "^3.0.2"
jest-haste-map "^27.0.2"
jest-resolve "^27.0.2"
jest-resolve "^27.0.4"
jest-util "^27.0.2"
jest-worker "^27.0.2"
slash "^3.0.0"
@ -1958,15 +1958,15 @@
"@types/istanbul-lib-coverage" "^2.0.0"
collect-v8-coverage "^1.0.0"
"@jest/test-sequencer@^27.0.3":
version "27.0.3"
resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.0.3.tgz#2a8632b86a9a6f8900e514917cdab6a062e71049"
integrity sha512-DcLTzraZ8xLr5fcIl+CF14vKeBBpBrn55wFxI9Ju+dhEBdjRdJQ/Z/pLkMehkPZWIQ+rR23J8e+wFDkfjree0Q==
"@jest/test-sequencer@^27.0.4":
version "27.0.4"
resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.0.4.tgz#976493b277594d81e589896f0ed21f198308928a"
integrity sha512-6UFEVwdmxYdyNffBxVVZxmXEdBE4riSddXYSnFNH0ELFQFk/bvagizim8WfgJTqF4EKd+j1yFxvhb8BMHfOjSQ==
dependencies:
"@jest/test-result" "^27.0.2"
graceful-fs "^4.2.4"
jest-haste-map "^27.0.2"
jest-runtime "^27.0.3"
jest-runtime "^27.0.4"
"@jest/transform@^27.0.2":
version "27.0.2"
@ -6243,13 +6243,13 @@ eslint-visitor-keys@^2.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
eslint@7.27.0:
version "7.27.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.27.0.tgz#665a1506d8f95655c9274d84bd78f7166b07e9c7"
integrity sha512-JZuR6La2ZF0UD384lcbnd0Cgg6QJjiCwhMD6eU4h/VGPcVGwawNNzKU41tgokGXnfjOOyI6QIffthhJTPzzuRA==
eslint@7.28.0:
version "7.28.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.28.0.tgz#435aa17a0b82c13bb2be9d51408b617e49c1e820"
integrity sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==
dependencies:
"@babel/code-frame" "7.12.11"
"@eslint/eslintrc" "^0.4.1"
"@eslint/eslintrc" "^0.4.2"
ajv "^6.10.0"
chalk "^4.0.0"
cross-spawn "^7.0.2"
@ -6266,7 +6266,7 @@ eslint@7.27.0:
fast-deep-equal "^3.1.3"
file-entry-cache "^6.0.1"
functional-red-black-tree "^1.0.1"
glob-parent "^5.0.0"
glob-parent "^5.1.2"
globals "^13.6.0"
ignore "^4.0.6"
import-fresh "^3.0.0"
@ -7250,13 +7250,6 @@ glob-parent@^3.1.0:
is-glob "^3.1.0"
path-dirname "^1.0.0"
glob-parent@^5.0.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2"
integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==
dependencies:
is-glob "^4.0.1"
glob-parent@^5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
@ -7264,7 +7257,7 @@ glob-parent@^5.1.0:
dependencies:
is-glob "^4.0.1"
glob-parent@~5.1.0:
glob-parent@^5.1.2, glob-parent@~5.1.0:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
@ -7307,13 +7300,6 @@ globals@^11.1.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
globals@^12.1.0:
version "12.3.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-12.3.0.tgz#1e564ee5c4dded2ab098b0f88f24702a3c56be13"
integrity sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==
dependencies:
type-fest "^0.8.1"
globals@^13.6.0:
version "13.6.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-13.6.0.tgz#d77138e53738567bb96a3916ff6f6b487af20ef7"
@ -7321,6 +7307,13 @@ globals@^13.6.0:
dependencies:
type-fest "^0.20.2"
globals@^13.9.0:
version "13.9.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb"
integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==
dependencies:
type-fest "^0.20.2"
globby@^11.0.2:
version "11.0.2"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.2.tgz#1af538b766a3b540ebfb58a32b2e2d5897321d83"
@ -7619,10 +7612,10 @@ hex-color-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
highlight.js@11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.0.0.tgz#e22ac9ca45edc4f87a2187685d591a108ceb8449"
integrity sha512-ByaTMfsSuoqerTwemOgpIhfULEIaK52JJYhky/sK7/Yqc0+t7Uh5DHay9vIC94YXSupnQ1Vqfc9VXrYP4eXW3Q==
highlight.js@11.0.1:
version "11.0.1"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.0.1.tgz#a78bafccd9aa297978799fe5eed9beb7ee1ef887"
integrity sha512-EqYpWyTF2s8nMfttfBA2yLKPNoZCO33pLS4MnbXQ4hECf1TKujCt1Kq7QAdrio7roL4+CqsfjqwYj4tYgq0pJQ==
highlight.js@^9.6.0:
version "9.17.1"
@ -8554,10 +8547,10 @@ jest-changed-files@^27.0.2:
execa "^5.0.0"
throat "^6.0.1"
jest-circus@^27.0.3:
version "27.0.3"
resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.0.3.tgz#32006967de484e03589da944064d72e172ce3261"
integrity sha512-tdMfzs7SgD5l7jRcI1iB3vtQi5fHwCgo4RlO8bzZnYc05PZ+tlAOMZeS8eGYkZ2tPaRY/aRLMFWQp/8zXBrolQ==
jest-circus@^27.0.4:
version "27.0.4"
resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.0.4.tgz#3b261514ee3b3da33def736a6352c98ff56bb6e6"
integrity sha512-QD+eblDiRphta630WRKewuASLs/oY1Zki2G4bccntRvrTHQ63ljwFR5TLduuK4Zg0ZPzW0+8o6AP7KRd1yKOjw==
dependencies:
"@jest/environment" "^27.0.3"
"@jest/test-result" "^27.0.2"
@ -8571,39 +8564,39 @@ jest-circus@^27.0.3:
jest-each "^27.0.2"
jest-matcher-utils "^27.0.2"
jest-message-util "^27.0.2"
jest-runtime "^27.0.3"
jest-snapshot "^27.0.2"
jest-runtime "^27.0.4"
jest-snapshot "^27.0.4"
jest-util "^27.0.2"
pretty-format "^27.0.2"
slash "^3.0.0"
stack-utils "^2.0.3"
throat "^6.0.1"
jest-cli@^27.0.3:
version "27.0.3"
resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.0.3.tgz#b733871acb526054a0f8c971d0466595c5f8316d"
integrity sha512-7bt9Sgv4nWH5pUnyJfdLf8CHWfo4+7lSPxeBwQx4r0vBj9jweJam/piE2U91SXtQI+ckm+TIN97OVnqIYpVhSg==
jest-cli@^27.0.4:
version "27.0.4"
resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.0.4.tgz#491b12c754c0d7c6873b13a66f26b3a80a852910"
integrity sha512-E0T+/i2lxsWAzV7LKYd0SB7HUAvePqaeIh5vX43/G5jXLhv1VzjYzJAGEkTfvxV774ll9cyE2ljcL73PVMEOXQ==
dependencies:
"@jest/core" "^27.0.3"
"@jest/core" "^27.0.4"
"@jest/test-result" "^27.0.2"
"@jest/types" "^27.0.2"
chalk "^4.0.0"
exit "^0.1.2"
graceful-fs "^4.2.4"
import-local "^3.0.2"
jest-config "^27.0.3"
jest-config "^27.0.4"
jest-util "^27.0.2"
jest-validate "^27.0.2"
prompts "^2.0.1"
yargs "^16.0.3"
jest-config@^27.0.3:
version "27.0.3"
resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.0.3.tgz#31871583573c6d669dcdb5bb2d1a8738f3b91c20"
integrity sha512-zgtI2YQo+ekKsmYNyDlXFY/7w7WWBSJFoj/WRe173WB88CDUrEYWr0sLdbLOQe+sRu6l1Y2S0MCS6BOJm5jkoA==
jest-config@^27.0.4:
version "27.0.4"
resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.0.4.tgz#c4f41378acf40ca77860fb4e213b12109d87b8cf"
integrity sha512-VkQFAHWnPQefdvHU9A+G3H/Z3NrrTKqWpvxgQz3nkUdkDTWeKJE6e//BL+R7z79dXOMVksYgM/z6ndtN0hfChg==
dependencies:
"@babel/core" "^7.1.0"
"@jest/test-sequencer" "^27.0.3"
"@jest/test-sequencer" "^27.0.4"
"@jest/types" "^27.0.2"
babel-jest "^27.0.2"
chalk "^4.0.0"
@ -8611,14 +8604,14 @@ jest-config@^27.0.3:
glob "^7.1.1"
graceful-fs "^4.2.4"
is-ci "^3.0.0"
jest-circus "^27.0.3"
jest-circus "^27.0.4"
jest-environment-jsdom "^27.0.3"
jest-environment-node "^27.0.3"
jest-get-type "^27.0.1"
jest-jasmine2 "^27.0.3"
jest-jasmine2 "^27.0.4"
jest-regex-util "^27.0.1"
jest-resolve "^27.0.2"
jest-runner "^27.0.3"
jest-resolve "^27.0.4"
jest-runner "^27.0.4"
jest-util "^27.0.2"
jest-validate "^27.0.2"
micromatch "^4.0.4"
@ -8702,10 +8695,10 @@ jest-haste-map@^27.0.2:
optionalDependencies:
fsevents "^2.3.2"
jest-jasmine2@^27.0.3:
version "27.0.3"
resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.0.3.tgz#fa6f6499566ea1b01b68b3ad13f49d1592b02c85"
integrity sha512-odJ2ia8P5c+IsqOcWJPmku4AqbXIfTVLRjYTKHri3TEvbmTdLw0ghy13OAPIl/0v7cVH0TURK7+xFOHKDLvKIA==
jest-jasmine2@^27.0.4:
version "27.0.4"
resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.0.4.tgz#c669519ccf4904a485338555e1e66cad36bb0670"
integrity sha512-yj3WrjjquZwkJw+eA4c9yucHw4/+EHndHWSqgHbHGQfT94ihaaQsa009j1a0puU8CNxPDk0c1oAPeOpdJUElwA==
dependencies:
"@babel/traverse" "^7.1.0"
"@jest/environment" "^27.0.3"
@ -8720,8 +8713,8 @@ jest-jasmine2@^27.0.3:
jest-each "^27.0.2"
jest-matcher-utils "^27.0.2"
jest-message-util "^27.0.2"
jest-runtime "^27.0.3"
jest-snapshot "^27.0.2"
jest-runtime "^27.0.4"
jest-snapshot "^27.0.4"
jest-util "^27.0.2"
pretty-format "^27.0.2"
throat "^6.0.1"
@ -8777,19 +8770,19 @@ jest-regex-util@^27.0.1:
resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.0.1.tgz#69d4b1bf5b690faa3490113c47486ed85dd45b68"
integrity sha512-6nY6QVcpTgEKQy1L41P4pr3aOddneK17kn3HJw6SdwGiKfgCGTvH02hVXL0GU8GEKtPH83eD2DIDgxHXOxVohQ==
jest-resolve-dependencies@^27.0.3:
version "27.0.3"
resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.0.3.tgz#7e258f7d0458bb910855f8a50f5c1e9d92c319dc"
integrity sha512-HdjWOvFAgT5CYChF2eiBN2rRKicjaTCCtA3EtH47REIdGzEHGUhYrWYgLahXsiOovvWN6edhcHL5WCa3gbc04A==
jest-resolve-dependencies@^27.0.4:
version "27.0.4"
resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.0.4.tgz#a07a242d70d668afd3fcf7f4270755eebb1fe579"
integrity sha512-F33UPfw1YGWCV2uxJl7wD6TvcQn5IC0LtguwY3r4L7R6H4twpLkp5Q2ZfzRx9A2I3G8feiy0O0sqcn/Qoym71A==
dependencies:
"@jest/types" "^27.0.2"
jest-regex-util "^27.0.1"
jest-snapshot "^27.0.2"
jest-snapshot "^27.0.4"
jest-resolve@^27.0.2:
version "27.0.2"
resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.0.2.tgz#087a3ed17182722a3415f92bfacc99c49cf8a965"
integrity sha512-rmfLGyZhwAUR5z3EwPAW7LQTorWAuCYCcsQJoQxT2it+BOgX3zKxa67r1pfpK3ihy2k9TjYD3/lMp5rPm/CL1Q==
jest-resolve@^27.0.4:
version "27.0.4"
resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.0.4.tgz#8a27bc3f2f00c8ea28f3bc99bbf6f468300a703d"
integrity sha512-BcfyK2i3cG79PDb/6gB6zFeFQlcqLsQjGBqznFCpA0L/3l1L/oOsltdUjs5eISAWA9HS9qtj8v2PSZr/yWxONQ==
dependencies:
"@jest/types" "^27.0.2"
chalk "^4.0.0"
@ -8801,10 +8794,10 @@ jest-resolve@^27.0.2:
resolve "^1.20.0"
slash "^3.0.0"
jest-runner@^27.0.3:
version "27.0.3"
resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.0.3.tgz#d9747af3bee5a6ffaeb9e10b653263b780258b54"
integrity sha512-zH23uIIh1ro1JCD7XX1bQ0bQwXEsBzLX2UJVE/AVLsk4YJRmTfyXIzzRzBWRdnMHHg1NWkJ4fGs7eFP15IqZpQ==
jest-runner@^27.0.4:
version "27.0.4"
resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.0.4.tgz#2787170a9509b792ae129794f6944d27d5d12a4f"
integrity sha512-NfmvSYLCsCJk2AG8Ar2NAh4PhsJJpO+/r+g4bKR5L/5jFzx/indUpnVBdrfDvuqhGLLAvrKJ9FM/Nt8o1dsqxg==
dependencies:
"@jest/console" "^27.0.2"
"@jest/environment" "^27.0.3"
@ -8817,20 +8810,22 @@ jest-runner@^27.0.3:
exit "^0.1.2"
graceful-fs "^4.2.4"
jest-docblock "^27.0.1"
jest-environment-jsdom "^27.0.3"
jest-environment-node "^27.0.3"
jest-haste-map "^27.0.2"
jest-leak-detector "^27.0.2"
jest-message-util "^27.0.2"
jest-resolve "^27.0.2"
jest-runtime "^27.0.3"
jest-resolve "^27.0.4"
jest-runtime "^27.0.4"
jest-util "^27.0.2"
jest-worker "^27.0.2"
source-map-support "^0.5.6"
throat "^6.0.1"
jest-runtime@^27.0.3:
version "27.0.3"
resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.0.3.tgz#32499c1047e5d953cfbb67fe790ab0167a614d28"
integrity sha512-k1Hl2pWWHBkSXdCggX2lyLRuDnnnmMlnJd+DPLb8LmmAeHW87WgGC6TplD377VxY3KQu73sklkhGUIdwFgsRVQ==
jest-runtime@^27.0.4:
version "27.0.4"
resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.0.4.tgz#2e4a6aa77cac32ac612dfe12768387a8aa15c2f0"
integrity sha512-voJB4xbAjS/qYPboV+e+gmg3jfvHJJY4CagFWBOM9dQKtlaiTjcpD2tWwla84Z7PtXSQPeIpXY0qksA9Dum29A==
dependencies:
"@jest/console" "^27.0.2"
"@jest/environment" "^27.0.3"
@ -8851,8 +8846,8 @@ jest-runtime@^27.0.3:
jest-message-util "^27.0.2"
jest-mock "^27.0.3"
jest-regex-util "^27.0.1"
jest-resolve "^27.0.2"
jest-snapshot "^27.0.2"
jest-resolve "^27.0.4"
jest-snapshot "^27.0.4"
jest-util "^27.0.2"
jest-validate "^27.0.2"
slash "^3.0.0"
@ -8867,10 +8862,10 @@ jest-serializer@^27.0.1:
"@types/node" "*"
graceful-fs "^4.2.4"
jest-snapshot@^27.0.2:
version "27.0.2"
resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.0.2.tgz#40c48dc6afd3cbc5d3d07c061f20fc10d94ca0cd"
integrity sha512-4RcgvZbPrrbEE/hT6XQ4hr+NVVLNrmsgUnYSnZRT6UAvW9Q2yzGMS+tfJh+xlQJAapnnkNJzsMn6vUa+yfiVHA==
jest-snapshot@^27.0.4:
version "27.0.4"
resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.0.4.tgz#2b96e22ca90382b3e93bd0aae2ce4c78bf51fb5b"
integrity sha512-hnjrvpKGdSMvKfbHyaG5Kul7pDJGZvjVy0CKpzhu28MmAssDXS6GpynhXzgst1wBQoKD8c9b2VS2a5yhDLQRCA==
dependencies:
"@babel/core" "^7.7.2"
"@babel/generator" "^7.7.2"
@ -8891,7 +8886,7 @@ jest-snapshot@^27.0.2:
jest-haste-map "^27.0.2"
jest-matcher-utils "^27.0.2"
jest-message-util "^27.0.2"
jest-resolve "^27.0.2"
jest-resolve "^27.0.4"
jest-util "^27.0.2"
natural-compare "^1.4.0"
pretty-format "^27.0.2"
@ -8943,14 +8938,14 @@ jest-worker@^27.0.2:
merge-stream "^2.0.0"
supports-color "^8.0.0"
jest@27.0.3:
version "27.0.3"
resolved "https://registry.yarnpkg.com/jest/-/jest-27.0.3.tgz#0b4ac738c93612f778d58250aee026220487e5a4"
integrity sha512-0G9+QqXFIZWgf5rs3yllpaA+13ZawVHfyuhuCV1EnoFbX++rVMRrYWCAnk+dfhwyv9/VTQvn+XG969u8aPRsBg==
jest@27.0.4:
version "27.0.4"
resolved "https://registry.yarnpkg.com/jest/-/jest-27.0.4.tgz#91d4d564b36bcf93b98dac1ab19f07089e670f53"
integrity sha512-Px1iKFooXgGSkk1H8dJxxBIrM3tsc5SIuI4kfKYK2J+4rvCvPGr/cXktxh0e9zIPQ5g09kOMNfHQEmusBUf/ZA==
dependencies:
"@jest/core" "^27.0.3"
"@jest/core" "^27.0.4"
import-local "^3.0.2"
jest-cli "^27.0.3"
jest-cli "^27.0.4"
joi@^17.3.0:
version "17.3.0"
@ -11976,10 +11971,10 @@ sass-loader@10.2.0:
schema-utils "^3.0.0"
semver "^7.3.2"
sass@1.34.0:
version "1.34.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.34.0.tgz#e46d5932d8b0ecc4feb846d861f26a578f7f7172"
integrity sha512-rHEN0BscqjUYuomUEaqq3BMgsXqQfkcMVR7UhscsAVub0/spUrZGBMxQXFS2kfiDsPLZw5yuU9iJEFNC2x38Qw==
sass@1.34.1:
version "1.34.1"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.34.1.tgz#30f45c606c483d47b634f1e7371e13ff773c96ef"
integrity sha512-scLA7EIZM+MmYlej6sdVr0HRbZX5caX5ofDT9asWnUJj21oqgsC+1LuNfm0eg+vM0fCTZHhwImTiCU0sx9h9CQ==
dependencies:
chokidar ">=3.0.0 <4.0.0"
@ -13690,10 +13685,10 @@ vue-style-loader@^4.1.0, vue-style-loader@^4.1.2:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
vue-template-compiler@2.6.13:
version "2.6.13"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.13.tgz#a735b8974e013ce829e7f77e08e4ee5aecbd3005"
integrity sha512-latKAqpUjCkovB8XppW5gnZbSdYQzkf8pavsMBZYZrQcG6lAnj0EH4Ty7jMwAwFw5Cf4mybKBHlp1UTjnLPOWw==
vue-template-compiler@2.6.14:
version "2.6.14"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz#a2f0e7d985670d42c9c9ee0d044fed7690f4f763"
integrity sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
@ -13703,10 +13698,10 @@ vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
vue@2.6.13:
version "2.6.13"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.13.tgz#94b2c1b31fddf1dfcc34f28ec848ba8f01ea4c5b"
integrity sha512-O+pAdJkce1ooYS1XyoQtpBQr9An+Oys3w39rkqxukVO3ZD1ilYJkWBGoRuadiQEm2LLJnCL2utV4TMSf52ubjw==
vue@2.6.14:
version "2.6.14"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==
vue@^2.6.11:
version "2.6.11"