Compare commits

...

14 Commits

Author SHA1 Message Date
grnd-alt
981d1cbd38 fix: remove deprecated ToolTip
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-14 09:54:43 +02:00
grnd-alt
85af54cd6f fix: do not clean webpack-stats.json from build files
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-09 14:38:28 +02:00
grnd-alt
f3a8de2c48 fix: stabilize cypress
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-09 14:38:22 +02:00
grnd-alt
1d7bf30e8f bump @nextcloud/dialogs to 7.0.0-rc.1
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-09 14:12:31 +02:00
grnd-alt
ee10097c24 fix: adapt NcButtons to nextcloud-vue 9
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-09 14:12:31 +02:00
grnd-alt
03d2bd945c fix: remove extra component for NcAction entries
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-09 14:12:29 +02:00
grnd-alt
1110a4f125 use correct prop name for nextcloud/vue vue3
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-09 14:10:39 +02:00
grnd-alt
f4acf38035 replace Vue.set with direct setting
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-09 14:10:39 +02:00
grnd-alt
30e74a7919 fix: rename parameter names deprecated in vue3
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-09 14:10:39 +02:00
grnd-alt
b59b2edb37 use v-model instead of value:sync
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-09 14:10:39 +02:00
grnd-alt
8e805dbec3 fix: use vue3 slot syntax
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-09 14:10:39 +02:00
grnd-alt
0f1d1181e3 fix: correct syntax for scss variables
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-09 14:10:39 +02:00
grnd-alt
c32115dea6 vue3-with non-local dependencies and part of the functionality
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-09 14:10:39 +02:00
grnd-alt
452dc5f230 get deck to build with vue3 with broken functionality
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-09 14:10:38 +02:00
45 changed files with 8020 additions and 6299 deletions

View File

@@ -84,7 +84,7 @@ describe('Card', function () {
cy.get('.modal-mask.card-selector .multiselect-list').should('be.visible').click() cy.get('.modal-mask.card-selector .multiselect-list').should('be.visible').click()
cy.get('.vs__dropdown-menu span[title="TestList"]').should('be.visible').click() cy.get('.vs__dropdown-menu span[title="TestList"]').should('be.visible').click()
cy.get('.modal-mask.card-selector button.button-vue--vue-primary').should('be.visible').click() cy.get('.modal-mask.card-selector button.button-vue--primary').contains('Create card').should('be.visible').click()
cy.wait('@save', { timeout: 7000 }) cy.wait('@save', { timeout: 7000 })
cy.reload() cy.reload()
@@ -187,7 +187,7 @@ describe('Card', function () {
cy.get('.file-picker__main').should('be.visible') cy.get('.file-picker__main').should('be.visible')
cy.get('.file-picker__main [data-filename="welcome.txt"]', { timeout: 30000 }).should('be.visible') cy.get('.file-picker__main [data-filename="welcome.txt"]', { timeout: 30000 }).should('be.visible')
.click() .click()
cy.get('.dialog__actions button.button-vue--vue-primary').click() cy.get('.dialog__actions button.button-vue--primary').click()
cy.get('.attachment-list .filename').contains('welcome') cy.get('.attachment-list .filename').contains('welcome')
cy.get('.attachment-list .filename .extension').contains('txt') cy.get('.attachment-list .filename .extension').contains('txt')
}) })
@@ -302,8 +302,10 @@ describe('Card', function () {
.first().click() .first().click()
cy.get(`.card:contains("${newCardTitle}")`).should('be.visible').click() cy.get(`.card:contains("${newCardTitle}")`).should('be.visible').click()
cy.get('#app-sidebar-vue [data-test="tag-selector"] .vs__dropdown-toggle').should('be.visible').click() cy.get('#app-sidebar-vue [data-test="tag-selector"] .vs__dropdown-toggle .vs__actions').should('be.visible').click()
cy.get('.vs__dropdown-menu .tag:contains("Action needed")').should('be.visible').click() cy.get('.vs__dropdown-menu .tag:contains("Action needed")').should('be.visible').click()
cy.get('.vs__dropdown-menu .tag:contains("Later")').should('not.exist')
cy.get('#app-sidebar-vue [data-test="tag-selector"] .vs__dropdown-toggle .vs__actions').should('be.visible').click()
cy.get('.vs__dropdown-menu .tag:contains("Later")').should('be.visible').click() cy.get('.vs__dropdown-menu .tag:contains("Later")').should('be.visible').click()
cy.get('.vs__selected .tag:contains("Action needed")').should('be.visible') cy.get('.vs__selected .tag:contains("Action needed")').should('be.visible')

View File

@@ -27,7 +27,7 @@ describe('Deck dashboard', function() {
const defaultBoard = 'Welcome to Nextcloud Deck!' const defaultBoard = 'Welcome to Nextcloud Deck!'
cy.get('.app-navigation-entry-wrapper[icon=icon-deck]') cy.get('#deck-navigation-all')
.find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + defaultBoard + ')') .find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + defaultBoard + ')')
.first() .first()
.contains(defaultBoard) .contains(defaultBoard)

View File

@@ -93,7 +93,7 @@ Cypress.Commands.add('createExampleBoard', ({ user, board }) => {
}) })
Cypress.Commands.add('getNavigationEntry', (boardTitle) => { Cypress.Commands.add('getNavigationEntry', (boardTitle) => {
return cy.get('.app-navigation-entry-wrapper[icon=icon-deck]') return cy.get('#deck-navigation-all')
.find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + boardTitle + ')') .find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + boardTitle + ')')
.find('a.app-navigation-entry-link') .find('a.app-navigation-entry-link')
}) })

13089
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,7 @@
"@nextcloud/auth": "^2.4.0", "@nextcloud/auth": "^2.4.0",
"@nextcloud/axios": "^2.5.1", "@nextcloud/axios": "^2.5.1",
"@nextcloud/capabilities": "^1.2.0", "@nextcloud/capabilities": "^1.2.0",
"@nextcloud/dialogs": "^6.0.1", "@nextcloud/dialogs": "^7.0.0-rc.1",
"@nextcloud/event-bus": "^3.3.2", "@nextcloud/event-bus": "^3.3.2",
"@nextcloud/files": "^3.10.1", "@nextcloud/files": "^3.10.1",
"@nextcloud/initial-state": "^2.2.0", "@nextcloud/initial-state": "^2.2.0",
@@ -43,7 +43,10 @@
"@nextcloud/moment": "^1.3.2", "@nextcloud/moment": "^1.3.2",
"@nextcloud/notify_push": "^1.3.0", "@nextcloud/notify_push": "^1.3.0",
"@nextcloud/router": "^3.0.1", "@nextcloud/router": "^3.0.1",
"@nextcloud/vue": "^8.25.1", "@nextcloud/vue": "^9.0.0-rc.3",
"@vue/compiler-sfc": "^3.5.13",
"@vue/vue3-jest": "^29.2.6",
"@vueuse/core": "^13.1.0",
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
"chroma-js": "^3.1.2", "chroma-js": "^3.1.2",
"dompurify": "^3.2.5", "dompurify": "^3.2.5",
@@ -54,16 +57,14 @@
"moment": "^2.30.1", "moment": "^2.30.1",
"p-queue": "^8.0.1", "p-queue": "^8.0.1",
"url-search-params-polyfill": "^8.2.5", "url-search-params-polyfill": "^8.2.5",
"vue": "^2.7.15", "v3-infinite-loading": "^1.3.2",
"vue-at": "^2.5.1", "vue": "^3.5.13",
"vue-click-outside": "^1.1.0", "vue-click-outside": "^1.1.0",
"vue-easymde": "^2.0.0", "vue-easymde": "^2.0.0",
"vue-infinite-loading": "^2.4.5",
"vue-material-design-icons": "^5.3.1", "vue-material-design-icons": "^5.3.1",
"vue-router": "^3.6.5", "vue-router": "^4.5.0",
"vue-smooth-dnd": "^0.8.1", "vue3-smooth-dnd": "^0.0.6",
"vuex": "^3.6.2", "vuex": "^4.1.0"
"vuex-router-sync": "^5.0.0"
}, },
"browserslist": [ "browserslist": [
"extends @nextcloud/browserslist-config" "extends @nextcloud/browserslist-config"
@@ -78,17 +79,15 @@
"@nextcloud/cypress": "^1.0.0-beta.13", "@nextcloud/cypress": "^1.0.0-beta.13",
"@nextcloud/eslint-config": "^8.4.2", "@nextcloud/eslint-config": "^8.4.2",
"@nextcloud/stylelint-config": "^3.0.1", "@nextcloud/stylelint-config": "^3.0.1",
"@nextcloud/webpack-vue-config": "^6.3.0", "@nextcloud/webpack-vue-config": "github:nextcloud-libraries/webpack-vue-config#vue3",
"@relative-ci/agent": "^4.3.1", "@relative-ci/agent": "^4.2.14",
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"@vue/vue2-jest": "^29.2.6",
"cypress": "^13.17.0", "cypress": "^13.17.0",
"eslint-plugin-cypress": "^3.6.0", "eslint-plugin-cypress": "^3.6.0",
"eslint-webpack-plugin": "^5.0.1", "eslint-webpack-plugin": "^5.0.1",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-serializer-vue": "^3.1.0", "jest-serializer-vue": "^3.1.0",
"stylelint-webpack-plugin": "^5.0.1", "stylelint-webpack-plugin": "^5.0.1"
"vue-template-compiler": "^2.7.16"
}, },
"jest": { "jest": {
"moduleFileExtensions": [ "moduleFileExtensions": [

View File

@@ -1,15 +1,51 @@
<!-- <!--
- SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors - SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later - SPDX-License-Identifier: AGPL-3.0-or-later
--> -->
<script>
import { NcAppContent, NcContent, NcModal } from '@nextcloud/vue'
import CardMoveDialog from './CardMoveDialog.vue'
import AppNavigation from './components/navigation/AppNavigation.vue'
import KeyboardShortcuts from './components/KeyboardShortcuts.vue'
import { BoardApi } from './services/BoardApi.js'
const boardApi = new BoardApi()
export default {
name: 'App',
components: {
NcContent,
AppNavigation,
NcAppContent,
KeyboardShortcuts,
CardMoveDialog,
NcModal,
},
provide() {
return {
boardApi,
}
},
computed: {
cardDetailsInModal() {
return this.$store.getters.config('cardDetailsInModal')
},
},
methods: {
hideModal() {
this.$router.push({ name: 'board' })
},
},
}
</script>
<template> <template>
<NcContent app-name="deck" :class="{ 'nav-hidden': !navShown, 'sidebar-hidden': !sidebarRouterView }"> <NcContent app-name="deck">
<AppNavigation /> <AppNavigation />
<NcAppContent :allow-swipe-navigation="false"> <NcAppContent :allow-swipe-navigation="false">
<router-view /> <router-view />
</NcAppContent> </NcAppContent>
<div v-if="$route.params.id || $route.params.cardId"> <div v-if="$route.params.id || $route.params.cardId">
<NcModal v-if="cardDetailsInModal && $route.params.cardId" <NcModal v-if="cardDetailsInModal && $route.params.cardId"
:name="t('deck', 'Card details')" :name="t('deck', 'Card details')"
@@ -21,179 +57,10 @@
<router-view name="sidebar" /> <router-view name="sidebar" />
</div> </div>
</NcModal> </NcModal>
<router-view name="sidebar" :visible="!cardDetailsInModal || !$route.params.cardId" /> <router-view name="sidebar" :visible="!cardDetailsInModal || !$route.params.cardId" />
</div> </div>
<KeyboardShortcuts /> <KeyboardShortcuts />
<CardMoveDialog /> <CardMoveDialog />
</NcContent> </NcContent>
</template> </template>
<script>
import { mapState } from 'vuex'
import AppNavigation from './components/navigation/AppNavigation.vue'
import KeyboardShortcuts from './components/KeyboardShortcuts.vue'
import { NcModal, NcContent, NcAppContent, isMobile } from '@nextcloud/vue'
import { BoardApi } from './services/BoardApi.js'
import { emit, subscribe } from '@nextcloud/event-bus'
import { loadState } from '@nextcloud/initial-state'
import CardMoveDialog from './CardMoveDialog.vue'
const boardApi = new BoardApi()
export default {
name: 'App',
components: {
CardMoveDialog,
AppNavigation,
NcModal,
NcContent,
NcAppContent,
KeyboardShortcuts,
},
mixins: [isMobile],
provide() {
return {
boardApi,
}
},
data() {
return {
addButton: {
icon: 'icon-add',
classes: [],
text: t('deck', 'Add board'),
edit: {
text: t('deck', 'Add board'),
action: () => {
},
reset: () => {
},
},
action: () => {
this.addButton.classes.push('editing')
},
},
}
},
computed: {
...mapState({
navShown: state => state.navShown,
sidebarShownState: state => state.sidebarShown,
currentBoard: state => state.currentBoard,
}),
// TODO: properly handle sidebar showing for route subview and board sidebar
sidebarRouterView() {
// console.log(this.$route)
return this.$route.name === 'card' || this.$route.name === 'board.details'
},
sidebarShown() {
return this.sidebarRouterView || this.sidebarShownState
},
cardDetailsInModal: {
get() {
return this.$store.getters.config('cardDetailsInModal')
},
set(newValue) {
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
},
},
},
created() {
const initialState = loadState('deck', 'initialBoards', null)
if (initialState !== null) {
this.$store.dispatch('loadBoards')
}
this.$store.dispatch('loadSharees')
},
mounted() {
// Set navigation to initial state and update in case it gets toggled
emit('toggle-navigation', { open: !this.isMobile && this.navShown, _initial: true })
this.$nextTick(() => {
subscribe('navigation-toggled', (navState) => {
this.$store.dispatch('toggleNav', navState.open)
})
})
},
methods: {
hideModal() {
this.$router.push({ name: 'board' })
},
},
}
</script>
<style lang="scss" scoped>
#content-vue {
#app-content {
transition: margin-left 100ms ease;
position: relative;
overflow-x: hidden;
align-items: stretch;
}
#app-sidebar {
transition: max-width 100ms ease;
}
&.nav-hidden {
#app-content {
margin-left: 0;
}
}
&.sidebar-hidden {
#app-sidebar {
max-width: 0;
min-width: 0;
}
}
}
</style>
<style lang="scss">
@import '../css/print';
.icon-activity {
background-image: url(../img/activity-dark.svg);
body[data-theme-dark] & {
background-image: url(../img/activity.svg);
}
}
.avatardiv.circles {
background: var(--color-primary-element);
}
.icon-circles {
background-image: url(../img/circles-dark.svg);
opacity: 1;
background-size: 20px;
background-position: center center;
}
.icon-circles-white, .icon-circles.icon-white {
background-image: url(../img/circles.svg);
opacity: 1;
background-size: 20px;
background-position: center center;
}
.icon-colorpicker {
background-image: url('../img/color_picker.svg');
}
.v-select {
width: 100%;
}
.modal__card {
width: 100%;
min-width: 100%;
height: calc(100% - 20px);
overflow: hidden;
}
</style>

View File

@@ -22,10 +22,10 @@
label="title" /> label="title" />
</div> </div>
<template #actions> <template #actions>
<NcButton :disabled="!isBoardAndStackChoosen" type="secondary" @click="moveCard"> <NcButton :disabled="!isBoardAndStackChoosen" variant="secondary" @click="moveCard">
{{ t('deck', 'Move card') }} {{ t('deck', 'Move card') }}
</NcButton> </NcButton>
<NcButton :disabled="!isBoardAndStackChoosen" type="primary" @click="cloneCard"> <NcButton :disabled="!isBoardAndStackChoosen" variant="primary" @click="cloneCard">
{{ t('deck', 'Copy card') }} {{ t('deck', 'Copy card') }}
</NcButton> </NcButton>
</template> </template>
@@ -72,7 +72,7 @@ export default {
mounted() { mounted() {
subscribe('deck:card:show-move-dialog', this.openModal) subscribe('deck:card:show-move-dialog', this.openModal)
}, },
destroyed() { unmounted() {
unsubscribe('deck:card:show-move-dialog', this.openModal) unsubscribe('deck:card:show-move-dialog', this.openModal)
}, },
methods: { methods: {

View File

@@ -10,9 +10,15 @@
:key="activity.activity_id" :key="activity.activity_id"
:activity="activity" /> :activity="activity" />
<InfiniteLoading :identifier="objectId" @infinite="infiniteHandler" @change="changeObject"> <InfiniteLoading :identifier="objectId" @infinite="infiniteHandler" @change="changeObject">
<div slot="spinner" class="icon-loading" /> <template #spinner>
<div slot="no-more" /> <div class="icon-loading" />
<div slot="no-results" /> </template>
<template #no-more>
<div />
</template>
<template #no-results>
<div />
</template>
</InfiniteLoading> </InfiniteLoading>
</div> </div>
</template> </template>
@@ -21,7 +27,7 @@
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router' import { generateOcsUrl } from '@nextcloud/router'
import ActivityEntry from './ActivityEntry.vue' import ActivityEntry from './ActivityEntry.vue'
import InfiniteLoading from 'vue-infinite-loading' // import InfiniteLoading from 'v3-infinite-loading'
const ACTIVITY_FETCH_LIMIT = 50 const ACTIVITY_FETCH_LIMIT = 50
@@ -29,7 +35,6 @@ export default {
name: 'ActivityList', name: 'ActivityList',
components: { components: {
ActivityEntry, ActivityEntry,
InfiniteLoading,
}, },
props: { props: {
filter: { filter: {

View File

@@ -84,7 +84,7 @@
:title="t('deck', 'Apply filter')" :title="t('deck', 'Apply filter')"
:aria-label="t('deck', 'Apply filter')" :aria-label="t('deck', 'Apply filter')"
class="filter-button" class="filter-button"
:type="isFilterActive ? 'primary' : 'tertiary'"> :variant="isFilterActive ? 'primary' : 'tertiary'">
<template #icon> <template #icon>
<FilterIcon v-if="isFilterActive" :size="20" decorative /> <FilterIcon v-if="isFilterActive" :size="20" decorative />
<FilterOffIcon v-else :size="20" decorative /> <FilterOffIcon v-else :size="20" decorative />
@@ -231,12 +231,16 @@
</NcActionButton> </NcActionButton>
<NcActionButton v-if="compactMode" <NcActionButton v-if="compactMode"
@click="toggleCompactMode"> @click="toggleCompactMode">
<ArrowExpandVerticalIcon slot="icon" :size="20" decorative /> <template #icon>
<ArrowExpandVerticalIcon :size="20" decorative />
</template>
{{ t('deck', 'Toggle compact mode') }} {{ t('deck', 'Toggle compact mode') }}
</NcActionButton> </NcActionButton>
<NcActionButton v-else <NcActionButton v-else
@click="toggleCompactMode"> @click="toggleCompactMode">
<ArrowCollapseVerticalIcon slot="icon" :size="20" decorative /> <template #icon>
<ArrowCollapseVerticalIcon :size="20" decorative />
</template>
{{ t('deck', 'Toggle compact mode') }} {{ t('deck', 'Toggle compact mode') }}
</NcActionButton> </NcActionButton>
<NcActionButton @click="toggleShowCardCover"> <NcActionButton @click="toggleShowCardCover">
@@ -274,6 +278,7 @@ import SessionList from './SessionList.vue'
import { isNotifyPushEnabled } from '../sessions.js' import { isNotifyPushEnabled } from '../sessions.js'
import CreateNewCardCustomPicker from '../views/CreateNewCardCustomPicker.vue' import CreateNewCardCustomPicker from '../views/CreateNewCardCustomPicker.vue'
import { getCurrentUser } from '@nextcloud/auth' import { getCurrentUser } from '@nextcloud/auth'
import { onClickOutside } from '@vueuse/core'
export default { export default {
name: 'Controls', name: 'Controls',
@@ -359,6 +364,11 @@ export default {
} }
}, },
}, },
created() {
onClickOutside(() => {
this.hideAddStack()
})
},
beforeMount() { beforeMount() {
subscribe('deck:board:show-new-card', this.clickShowAddCardModel) subscribe('deck:board:show-new-card', this.clickShowAddCardModel)
subscribe('deck:board:toggle-filter-popover', this.triggerOpenFilters) subscribe('deck:board:toggle-filter-popover', this.triggerOpenFilters)
@@ -366,7 +376,7 @@ export default {
subscribe('deck:board:toggle-filter-by-me', this.triggerFilterByMe) subscribe('deck:board:toggle-filter-by-me', this.triggerFilterByMe)
}, },
beforeDestroy() { beforeUnmount() {
unsubscribe('deck:board:show-new-card', this.clickShowAddCardModel) unsubscribe('deck:board:show-new-card', this.clickShowAddCardModel)
unsubscribe('deck:board:toggle-filter-popover', this.triggerOpenFilters) unsubscribe('deck:board:toggle-filter-popover', this.triggerOpenFilters)
unsubscribe('deck:board:clear-filter', this.triggerClearFilter) unsubscribe('deck:board:clear-filter', this.triggerClearFilter)

View File

@@ -6,7 +6,6 @@
<!-- :style="{top:cardTop, left:cardLeft}" --> <!-- :style="{top:cardTop, left:cardLeft}" -->
<div v-if="card && selector" <div v-if="card && selector"
ref="shortcutModal" ref="shortcutModal"
v-click-outside="close"
class="keyboard-shortcuts__modal" class="keyboard-shortcuts__modal"
tabindex="0" tabindex="0"
@keydown.esc="close"> @keydown.esc="close">
@@ -18,6 +17,7 @@
</template> </template>
<script> <script>
import DueDateSelector from './card/DueDateSelector.vue' import DueDateSelector from './card/DueDateSelector.vue'
import { onClickOutside } from '@vueuse/core'
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus' import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import TagSelector from './card/TagSelector.vue' import TagSelector from './card/TagSelector.vue'
@@ -50,8 +50,9 @@ export default {
subscribe('deck:card:show-assignment-selector', this.handleShowAssignemnt) subscribe('deck:card:show-assignment-selector', this.handleShowAssignemnt)
subscribe('deck:card:show-due-date-selector', this.handleShowDueDate) subscribe('deck:card:show-due-date-selector', this.handleShowDueDate)
subscribe('deck:card:show-label-selector', this.handleShowLabel) subscribe('deck:card:show-label-selector', this.handleShowLabel)
onClickOutside(this.close)
}, },
destroyed() { unmounted() {
document.removeEventListener('keydown', this.onKeydown) document.removeEventListener('keydown', this.onKeydown)
unsubscribe('deck:card:show-assignment-selector', this.handleShowAssignemnt) unsubscribe('deck:card:show-assignment-selector', this.handleShowAssignemnt)
unsubscribe('deck:card:show-due-date-selector', this.handleShowDueDate) unsubscribe('deck:card:show-due-date-selector', this.handleShowDueDate)

View File

@@ -29,12 +29,12 @@
{{ t('deck', 'Create a new list to add cards to this board') }} {{ t('deck', 'Create a new list to add cards to this board') }}
<form @submit.prevent="addNewStack()"> <form @submit.prevent="addNewStack()">
<NcTextField ref="newStackInput" <NcTextField ref="newStackInput"
v-model="newStackTitle"
:disable="loading" :disable="loading"
:value.sync="newStackTitle"
:placeholder="t('deck', 'List name')" :placeholder="t('deck', 'List name')"
type="text" /> type="text" />
<NcButton type="secondary" <NcButton variant="secondary"
native-type="submit" type="submit"
:disabled="loading" :disabled="loading"
:title="t('deck', 'Add list')"> :title="t('deck', 'Add list')">
<template #icon> <template #icon>
@@ -63,7 +63,10 @@
data-click-closes-sidebar="true" data-click-closes-sidebar="true"
data-dragscroll-enabled data-dragscroll-enabled
class="stack-draggable-wrapper"> class="stack-draggable-wrapper">
<Stack :stack="stack" :dragging="draggingStack" data-click-closes-sidebar="true" /> <Stack :stack="stack"
:dragging="draggingStack"
data-click-closes-sidebar="true"
@open-card="openCard" />
</Draggable> </Draggable>
</Container> </Container>
</div> </div>
@@ -82,7 +85,7 @@
</template> </template>
<script> <script>
import { Container, Draggable } from 'vue-smooth-dnd' import { Container, Draggable } from 'vue3-smooth-dnd'
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
import Controls from '../Controls.vue' import Controls from '../Controls.vue'
import DeckIcon from '../icons/DeckIcon.vue' import DeckIcon from '../icons/DeckIcon.vue'
@@ -165,14 +168,14 @@ export default {
created() { created() {
this.session = createSession(this.id) this.session = createSession(this.id)
this.fetchData() this.fetchData()
this.$root.$on('open-card', (cardId) => {
this.localModal = cardId
})
}, },
beforeDestroy() { beforeUnmount() {
this.session.close() this.session.close()
}, },
methods: { methods: {
openCard(cardId) {
},
async fetchData() { async fetchData() {
this.loading = true this.loading = true
try { try {
@@ -253,8 +256,8 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../css/animations'; @use '../../css/animations';
@import '../../css/variables'; @use '../../css/variables';
form { form {
text-align: center; text-align: center;
@@ -282,7 +285,7 @@ export default {
} }
.board { .board {
padding-left: $board-spacing; padding-left: variables.$board-spacing;
position: relative; position: relative;
max-height: calc(100% - var(--default-clickable-area)); max-height: calc(100% - var(--default-clickable-area));
overflow: hidden; overflow: hidden;
@@ -313,8 +316,8 @@ export default {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
// Margin left instead of padidng to avoid jumps on dropping a card // Margin left instead of padidng to avoid jumps on dropping a card
margin-left: $stack-spacing; margin-left: variables.$stack-spacing;
padding-right: $stack-spacing; padding-right: variables.$stack-spacing;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
padding-top: 15px; padding-top: 15px;

View File

@@ -94,7 +94,7 @@
non-drag-area-selector=".dragDisabled" non-drag-area-selector=".dragDisabled"
:drag-handle-selector="dragHandleSelector" :drag-handle-selector="dragHandleSelector"
data-dragscroll-enabled data-dragscroll-enabled
@should-accept-drop="canEdit" :should-accept-drop="() => canEdit"
@drag-start="draggingCard = true" @drag-start="draggingCard = true"
@drag-end="draggingCard = false" @drag-end="draggingCard = false"
@drop="($event) => onDropCard(stack.id, $event)"> @drop="($event) => onDropCard(stack.id, $event)">
@@ -102,7 +102,10 @@
<transition :appear="animate && !card.animated && (card.animated=true)" <transition :appear="animate && !card.animated && (card.animated=true)"
:appear-class="'zoom-appear-class'" :appear-class="'zoom-appear-class'"
:appear-active-class="'zoom-appear-active-class'"> :appear-active-class="'zoom-appear-active-class'">
<CardItem :id="card.id" ref="card" :dragging="draggingCard" /> <CardItem :id="card.id"
ref="card"
:dragging="draggingCard"
@open-card="openCard" />
</transition> </transition>
</Draggable> </Draggable>
</Container> </Container>
@@ -136,7 +139,7 @@
<script> <script>
import ClickOutside from 'vue-click-outside' import ClickOutside from 'vue-click-outside'
import { mapGetters, mapState } from 'vuex' import { mapGetters, mapState } from 'vuex'
import { Container, Draggable } from 'vue-smooth-dnd' import { Container, Draggable } from 'vue3-smooth-dnd'
import ArchiveIcon from 'vue-material-design-icons/Archive.vue' import ArchiveIcon from 'vue-material-design-icons/Archive.vue'
import CardPlusOutline from 'vue-material-design-icons/CardPlusOutline.vue' import CardPlusOutline from 'vue-material-design-icons/CardPlusOutline.vue'
import { NcActions, NcActionButton, NcModal } from '@nextcloud/vue' import { NcActions, NcActionButton, NcModal } from '@nextcloud/vue'
@@ -171,6 +174,9 @@ export default {
default: undefined, default: undefined,
}, },
}, },
emits: [
'open-card',
],
data() { data() {
return { return {
editing: false, editing: false,
@@ -233,6 +239,9 @@ export default {
}, },
methods: { methods: {
openCard(cardId) {
this.$emit('open-card', cardId)
},
stopCardCreation(e) { stopCardCreation(e) {
// For some reason the submit event triggers a MouseEvent that is bubbling to the outside // For some reason the submit event triggers a MouseEvent that is bubbling to the outside
// so we have to ignore it // so we have to ignore it
@@ -362,10 +371,10 @@ export default {
@use 'sass:math'; @use 'sass:math';
@import './../../css/variables'; @use './../../css/variables';
.stack { .stack {
width: $stack-width + $stack-spacing * 3; width: variables.$stack-width + variables.$stack-spacing * 3;
} }
.stack__header { .stack__header {
@@ -373,8 +382,8 @@ export default {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 100; z-index: 100;
padding-left: $card-spacing; padding-left: variables.$card-spacing;
padding-right: $card-spacing; padding-right: variables.$card-spacing;
margin: 6px; margin: 6px;
margin-top: 0; margin-top: 0;
cursor: grab; cursor: grab;
@@ -418,7 +427,7 @@ export default {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: calc($stack-width - 60px); max-width: calc(variables.$stack-width - 60px);
border-radius: 3px; border-radius: 3px;
padding: 4px 4px; padding: 4px 4px;
font-size: var(--default-font-size); font-size: var(--default-font-size);
@@ -459,8 +468,8 @@ export default {
form { form {
display: flex; display: flex;
margin-left: $stack-spacing; margin-left: variables.$stack-spacing;
margin-right: $stack-spacing; margin-right: variables.$stack-spacing;
width: 100%; width: 100%;
border: 2px solid var(--color-border-maxcontrast); border: 2px solid var(--color-border-maxcontrast);
border-radius: var(--border-radius-large); border-radius: var(--border-radius-large);

View File

@@ -3,15 +3,16 @@
- SPDX-License-Identifier: AGPL-3.0-or-later - SPDX-License-Identifier: AGPL-3.0-or-later
--> -->
<!-- eslint-disable vue/no-v-model-argument -->
<template> <template>
<NcAppSidebar v-if="currentBoard && currentCard" <NcAppSidebar v-if="currentBoard && currentCard"
ref="cardSidebar" ref="cardSidebar"
v-model:name-editable="isEditingTitle"
:active="tabId" :active="tabId"
:name="displayTitle" :name="displayTitle"
:subname="subtitle" :subname="subtitle"
:subtitle="subtitleTooltip" :subtitle="subtitleTooltip"
:name-editable.sync="isEditingTitle" @update:name="value => titleEditing = value"
@update:name="(value) => titleEditing = value"
@dismiss-editing="titleEditing = currentCard.title" @dismiss-editing="titleEditing = currentCard.title"
@submit-name="handleSubmitTitle" @submit-name="handleSubmitTitle"
@opened="focusHeader" @opened="focusHeader"
@@ -24,35 +25,69 @@
{{ t('deck', 'Open in bigger view') }} {{ t('deck', 'Open in bigger view') }}
</NcActionButton> </NcActionButton>
<CardMenuEntries :card="currentCard" :hide-details-entry="true" /> <NcActionButton v-if="canEdit && !isCurrentUserAssigned"
icon="icon-user"
:close-after-click="true"
@click="assignCardToMe()">
{{ t('deck', 'Assign to me') }}
</NcActionButton>
<NcActionButton v-if="canEdit && isCurrentUserAssigned"
icon="icon-user"
:close-after-click="true"
@click="unassignCardFromMe()">
{{ t('deck', 'Unassign myself') }}
</NcActionButton>
<NcActionButton v-if="canEdit"
icon="icon-checkmark"
:close-after-click="true"
@click="changeCardDoneStatus()">
{{ currentCard.done ? t('deck', 'Mark as not done') : t('deck', 'Mark as done') }}
</NcActionButton>
<NcActionButton v-if="canEdit"
icon="icon-external"
:close-after-click="true"
@click="openCardMoveDialog">
{{ t('deck', 'Move/copy card') }}
</NcActionButton>
<NcActionButton v-for="action in cardActions"
:key="action.label"
:close-after-click="true"
:icon="action.icon"
@click="action.callback(cardRichObject)">
{{ action.label }}
</NcActionButton>
<NcActionButton v-if="canEditBoard" :close-after-click="true" @click="archiveUnarchiveCard()">
<template #icon>
<ArchiveIcon :size="20" decorative />
</template>
{{ currentCard.archived ? t('deck', 'Unarchive card') : t('deck', 'Archive card') }}
</NcActionButton>
<NcActionButton v-if="canEdit"
icon="icon-delete"
:close-after-click="true"
@click="deleteCard()">
{{ t('deck', 'Delete card') }}
</NcActionButton>
</template> </template>
<template #description> <template #description>
<NcReferenceList v-if="currentCard.referenceData" <NcReferenceList v-if="currentCard.referenceData" :text="currentCard.title" :interactive="false" />
:text="currentCard.title"
:interactive="false" />
</template> </template>
<NcAppSidebarTab id="details" <NcAppSidebarTab id="details" :order="0" :name="t('deck', 'Details')">
:order="0"
:name="t('deck', 'Details')">
<CardSidebarTabDetails :card="currentCard" /> <CardSidebarTabDetails :card="currentCard" />
<template #icon> <template #icon>
<HomeIcon :size="20" /> <HomeIcon :size="20" />
</template> </template>
</NcAppSidebarTab> </NcAppSidebarTab>
<NcAppSidebarTab id="attachments" <NcAppSidebarTab id="attachments" :order="1" :name="t('deck', 'Attachments')">
:order="1"
:name="t('deck', 'Attachments')">
<template #icon> <template #icon>
<AttachmentIcon :size="20" /> <AttachmentIcon :size="20" />
</template> </template>
<CardSidebarTabAttachments :card="currentCard" /> <CardSidebarTabAttachments :card="currentCard" />
</NcAppSidebarTab> </NcAppSidebarTab>
<NcAppSidebarTab id="comments" <NcAppSidebarTab id="comments" :order="2" :name="t('deck', 'Comments')">
:order="2"
:name="t('deck', 'Comments')">
<template #icon> <template #icon>
<CommentIcon :size="20" /> <CommentIcon :size="20" />
</template> </template>
@@ -73,7 +108,7 @@
<script> <script>
import { NcActionButton, NcAppSidebar, NcAppSidebarTab } from '@nextcloud/vue' import { NcActionButton, NcAppSidebar, NcAppSidebarTab } from '@nextcloud/vue'
import { NcReferenceList } from '@nextcloud/vue/dist/Components/NcRichText.js' import { NcReferenceList } from '@nextcloud/vue/components/NcRichText'
import { getCapabilities } from '@nextcloud/capabilities' import { getCapabilities } from '@nextcloud/capabilities'
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
import CardSidebarTabDetails from './CardSidebarTabDetails.vue' import CardSidebarTabDetails from './CardSidebarTabDetails.vue'
@@ -87,9 +122,15 @@ import HomeIcon from 'vue-material-design-icons/Home.vue'
import CommentIcon from 'vue-material-design-icons/Comment.vue' import CommentIcon from 'vue-material-design-icons/Comment.vue'
import ActivityIcon from 'vue-material-design-icons/LightningBolt.vue' import ActivityIcon from 'vue-material-design-icons/LightningBolt.vue'
import { showError, showWarning } from '@nextcloud/dialogs' import { showError, showWarning, showUndo } from '@nextcloud/dialogs'
import { getLocale } from '@nextcloud/l10n' import { getLocale } from '@nextcloud/l10n'
import CardMenuEntries from '../cards/CardMenuEntries.vue' import { emit } from '@nextcloud/event-bus'
import ArchiveIcon from 'vue-material-design-icons/Archive.vue'
import { getCurrentUser } from '@nextcloud/auth'
import { generateUrl } from '@nextcloud/router'
import '@nextcloud/dialogs/style.css'
const capabilities = getCapabilities() const capabilities = getCapabilities()
@@ -108,7 +149,7 @@ export default {
AttachmentIcon, AttachmentIcon,
CommentIcon, CommentIcon,
HomeIcon, HomeIcon,
CardMenuEntries, ArchiveIcon,
}, },
mixins: [relativeDate], mixins: [relativeDate],
props: { props: {
@@ -140,8 +181,17 @@ export default {
isFullApp: (state) => state.isFullApp, isFullApp: (state) => state.isFullApp,
currentBoard: (state) => state.currentBoard, currentBoard: (state) => state.currentBoard,
hasCardSaveError: (state) => state.hasCardSaveError, hasCardSaveError: (state) => state.hasCardSaveError,
showArchived: (state) => state.showArchived,
}), }),
...mapGetters(['canEdit', 'assignables', 'cardActions', 'stackById']), ...mapGetters([
'canEdit',
'assignables',
'cardActions',
'stackById',
'isArchived',
'boards',
'boardById',
]),
currentCard() { currentCard() {
return this.$store.getters.cardById(this.id) return this.$store.getters.cardById(this.id)
}, },
@@ -168,6 +218,31 @@ export default {
return reference ? reference.openGraphObject.name : this.currentCard.title return reference ? reference.openGraphObject.name : this.currentCard.title
}, },
}, },
canEdit() {
return !this.currentCard.archived
},
canEditBoard() {
if (this.currentBoard) {
return this.$store.getters.canEdit
}
const board = this.$store.getters.boards.find((item) => item.id === this.currentCard.boardId)
return !!board?.permissions?.PERMISSION_EDIT
},
isCurrentUserAssigned() {
return this.currentCard.assignedUsers.find((item) => item.type === 0 && item.participant.uid === getCurrentUser()?.uid)
},
boardId() {
return this.card?.boardId ? this.currentCard.boardId : Number(this.$route.params.id)
},
cardRichObject() {
return {
id: '' + this.currentCard.id,
name: this.currentCard.title,
boardname: this.boardById(this.boardId)?.title,
stackname: this.stackById(this.currentCard.stackId)?.title,
link: window.location.protocol + '//' + window.location.host + generateUrl('/apps/deck/') + `card/${this.currentCard.id}`,
}
},
}, },
watch: { watch: {
currentCard() { currentCard() {
@@ -216,12 +291,46 @@ export default {
formatDate(timestamp) { formatDate(timestamp) {
return moment.unix(timestamp).locale(this.locale).format('LLLL') return moment.unix(timestamp).locale(this.locale).format('LLLL')
}, },
deleteCard() {
this.$store.dispatch('deleteCard', this.currentCard)
const undoCard = { ...this.currentCard, deletedAt: 0 }
showUndo(t('deck', 'Card deleted'), () => this.$store.dispatch('cardUndoDelete', undoCard))
if (this.$router.currentRoute.name === 'card') {
this.$router.push({ name: 'board' })
}
},
changeCardDoneStatus() {
this.$store.dispatch('changeCardDoneStatus', { ...this.currentCard, done: !this.currentCard.done })
},
archiveUnarchiveCard() {
this.$store.dispatch('archiveUnarchiveCard', { ...this.currentCard, archived: !this.currentCard.archived })
},
assignCardToMe() {
this.$store.dispatch('assignCardToUser', {
card: this.currentCard,
assignee: {
userId: getCurrentUser()?.uid,
type: 0,
},
})
},
unassignCardFromMe() {
this.$store.dispatch('removeUserFromCard', {
card: this.currentCard,
assignee: {
userId: getCurrentUser()?.uid,
type: 0,
},
})
},
openCardMoveDialog() {
emit('deck:card:show-move-dialog', this.currentCard)
},
}, },
} }
</script> </script>
<style lang="scss"> <style lang="scss">
section.app-sidebar__tab--active { section.app-sidebar__tab--active {
min-height: auto; min-height: auto;
display: flex; display: flex;
@@ -273,6 +382,7 @@ section.app-sidebar__tab--active {
z-index: 100; z-index: 100;
background-color: var(--color-main-background); background-color: var(--color-main-background);
} }
.app-sidebar-tabs__nav { .app-sidebar-tabs__nav {
position: sticky; position: sticky;
top: 87px; top: 87px;
@@ -285,10 +395,10 @@ section.app-sidebar__tab--active {
overflow: initial; overflow: initial;
} }
#emptycontent, .emptycontent { #emptycontent,
.emptycontent {
margin-top: 88px; margin-top: 88px;
} }
} }
} }
</style> </style>

View File

@@ -23,11 +23,11 @@
:key="comment.id" :key="comment.id"
:comment="comment" :comment="comment"
@doReload="loadComments" /> @doReload="loadComments" />
<InfiniteLoading :identifier="card.id" @infinite="infiniteHandler"> <!-- <InfiniteLoading :identifier="card.id" @infinite="infiniteHandler">
<div slot="spinner" class="icon-loading" /> <div slot="spinner" class="icon-loading" />
<div slot="no-more" /> <div slot="no-more" />
<div slot="no-results" /> <div slot="no-results" />
</InfiniteLoading> </InfiniteLoading> -->
</ul> </ul>
<div v-else-if="isLoading" class="icon icon-loading" /> <div v-else-if="isLoading" class="icon icon-loading" />
<div v-else class="emptycontent"> <div v-else class="emptycontent">
@@ -42,7 +42,7 @@ import { mapState, mapGetters } from 'vuex'
import { NcAvatar } from '@nextcloud/vue' import { NcAvatar } from '@nextcloud/vue'
import CommentItem from './CommentItem.vue' import CommentItem from './CommentItem.vue'
import CommentForm from './CommentForm.vue' import CommentForm from './CommentForm.vue'
import InfiniteLoading from 'vue-infinite-loading' // import InfiniteLoading from 'v3-infinite-loading'
import { getCurrentUser } from '@nextcloud/auth' import { getCurrentUser } from '@nextcloud/auth'
export default { export default {
@@ -51,7 +51,7 @@ export default {
NcAvatar, NcAvatar,
CommentItem, CommentItem,
CommentForm, CommentForm,
InfiniteLoading, // InfiniteLoading,
}, },
props: { props: {
card: { card: {

View File

@@ -2,8 +2,13 @@
- SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors - SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later - SPDX-License-Identifier: AGPL-3.0-or-later
--> -->
<template> <template>
<div>
THIS HAS AT
</div>
</template>
<!-- <template>
<div class="comment-form"> <div class="comment-form">
<form @submit.prevent="submit"> <form @submit.prevent="submit">
<At ref="at" <At ref="at"
@@ -191,4 +196,4 @@ export default {
.atwho-li--avatar { .atwho-li--avatar {
margin-right: 10px; margin-right: 10px;
} }
</style> </style> -->

View File

@@ -175,7 +175,7 @@ export default {
mounted() { mounted() {
this.setupEditor() this.setupEditor()
}, },
async beforeDestroy() { async beforeUnmount() {
await this.destroyEditor() await this.destroyEditor()
}, },
methods: { methods: {

View File

@@ -4,8 +4,12 @@
--> -->
<template> <template>
<CardDetailEntry :label="t('deck', 'Assign a due date to this card…')" data-test="due-date-selector"> <CardDetailEntry :label="t('deck', 'Assign a due date to this card…')" data-test="due-date-selector">
<Calendar v-if="!card.done" slot="icon" :size="20" /> <template v-if="!card.done" #icon>
<CalendarCheck v-else slot="icon" :size="20" /> <Calendar :size="20" />
</template>
<template v-else #icon>
<CalendarCheck :size="20" />
</template>
<template v-if="!card.done && !card.archived"> <template v-if="!card.done && !card.archived">
<NcDateTimePickerNative v-if="duedate" <NcDateTimePickerNative v-if="duedate"
id="card-duedate-picker" id="card-duedate-picker"
@@ -15,7 +19,7 @@
type="datetime-local" /> type="datetime-local" />
<NcActions v-if="canEdit" <NcActions v-if="canEdit"
:menu-title="!duedate ? t('deck', 'Add due date') : null" :menu-title="!duedate ? t('deck', 'Add due date') : null"
type="tertiary" variant="tertiary"
data-cy-due-date-actions> data-cy-due-date-actions>
<template v-if="!duedate" #icon> <template v-if="!duedate" #icon>
<Plus :size="20" /> <Plus :size="20" />
@@ -48,7 +52,7 @@
</NcActions> </NcActions>
<NcButton v-if="!card.done" <NcButton v-if="!card.done"
type="secondary" variant="secondary"
class="completed-button" class="completed-button"
@click="changeCardDoneStatus()"> @click="changeCardDoneStatus()">
<template #icon> <template #icon>
@@ -69,14 +73,14 @@
</div> </div>
<div class="due-actions"> <div class="due-actions">
<NcButton v-if="!card.archived" <NcButton v-if="!card.archived"
type="tertiary" variant="tertiary"
:name="t('deck', 'Not done')" :name="t('deck', 'Not done')"
@click="changeCardDoneStatus()"> @click="changeCardDoneStatus()">
<template #icon> <template #icon>
<ClearIcon :size="20" /> <ClearIcon :size="20" />
</template> </template>
</NcButton> </NcButton>
<NcButton type="secondary" @click="archiveUnarchiveCard()"> <NcButton variant="secondary" @click="archiveUnarchiveCard()">
<template #icon> <template #icon>
<ArchiveIcon :size="20" /> <ArchiveIcon :size="20" />
</template> </template>

View File

@@ -7,7 +7,7 @@
<div class="selector-wrapper--icon"> <div class="selector-wrapper--icon">
<TagMultiple :size="20" /> <TagMultiple :size="20" />
</div> </div>
<NcSelect :value="assignedLabels" <NcSelect v-model="assignedLabels"
class="selector-wrapper--selector" class="selector-wrapper--selector"
:multiple="true" :multiple="true"
:disabled="disabled" :disabled="disabled"

View File

@@ -72,7 +72,7 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../css/variables'; @use '../../css/variables';
.card-cover { .card-cover {
height: 90px; height: 90px;

View File

@@ -88,6 +88,7 @@ import CardMenu from './CardMenu.vue'
import CardCover from './CardCover.vue' import CardCover from './CardCover.vue'
import DueDate from './badges/DueDate.vue' import DueDate from './badges/DueDate.vue'
import { getCurrentUser } from '@nextcloud/auth' import { getCurrentUser } from '@nextcloud/auth'
import { emit } from '@nextcloud/event-bus'
const TITLE_EDITING_STATE = { const TITLE_EDITING_STATE = {
OFF: 0, OFF: 0,
@@ -120,6 +121,7 @@ export default {
default: false, default: false,
}, },
}, },
emits: ['open-card'],
data() { data() {
return { return {
highlight: false, highlight: false,
@@ -226,8 +228,9 @@ export default {
this.$router.push({ name: 'card', params: { id: boardId, cardId: this.card.id } }).catch(() => {}) this.$router.push({ name: 'card', params: { id: boardId, cardId: this.card.id } }).catch(() => {})
return return
} }
emit('open-card', {
this.$root.$emit('open-card', this.card.id) cardId: this.card.id,
})
}, },
triggerEditTitle() { triggerEditTitle() {
this.editingTitle = TITLE_EDITING_STATE.PENDING this.editingTitle = TITLE_EDITING_STATE.PENDING
@@ -318,8 +321,8 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import './../../css/animations'; @use './../../css/animations';
@import './../../css/variables'; @use './../../css/variables';
@mixin dark-card { @mixin dark-card {
border: 2px solid var(--color-border-dark); border: 2px solid var(--color-border-dark);
@@ -331,8 +334,8 @@ export default {
border-radius: var(--border-radius-large); border-radius: var(--border-radius-large);
font-size: 100%; font-size: 100%;
background-color: var(--color-main-background); background-color: var(--color-main-background);
margin-bottom: $card-spacing; margin-bottom: variables.$card-spacing;
padding: var(--default-grid-baseline) $card-padding; padding: var(--default-grid-baseline) variables.$card-padding;
border: 2px solid var(--color-border-dark); border: 2px solid var(--color-border-dark);
width: 100%; width: 100%;
display: flex; display: flex;
@@ -470,7 +473,7 @@ export default {
width: 32px; width: 32px;
} }
&.has-labels { &.has-labels {
padding-bottom: $card-padding; padding-bottom: variables.$card-padding;
} }
.labels { .labels {
height: 6px; height: 6px;

View File

@@ -6,40 +6,190 @@
<template> <template>
<div v-if="card" class="card-menu" @click.stop.prevent> <div v-if="card" class="card-menu" @click.stop.prevent>
<NcButton v-if="card.referenceData" <NcButton v-if="card.referenceData"
type="tertiary" variant="tertiary"
:title="t('deck','Open link')" :title="t('deck', 'Open link')"
@click="openLink"> @click="openLink">
<template #icon> <template #icon>
<LinkIcon :size="20" /> <LinkIcon :size="20" />
</template> </template>
</NcButton> </NcButton>
<NcActions> <NcActions>
<CardMenuEntries :card="card" @edit-title="editTitle" /> <NcActionButton v-if="!hideDetailsEntry" :close-after-click="true" @click="openCard">
<template #icon>
<CardBulletedIcon icon :size="20" decorative />
</template>
{{ t('deck', 'Card details') }}
</NcActionButton>
<NcActionButton v-if="canEdit" :close-after-click="true" @click="editTitle">
<template #icon>
<PencilIcon :size="20" decorative />
</template>
{{ t('deck', 'Edit title') }}
</NcActionButton>
<NcActionButton v-if="canEdit && !isCurrentUserAssigned"
icon="icon-user"
:close-after-click="true"
@click="assignCardToMe()">
{{ t('deck', 'Assign to me') }}
</NcActionButton>
<NcActionButton v-if="canEdit && isCurrentUserAssigned"
icon="icon-user"
:close-after-click="true"
@click="unassignCardFromMe()">
{{ t('deck', 'Unassign myself') }}
</NcActionButton>
<NcActionButton v-if="canEdit"
icon="icon-checkmark"
:close-after-click="true"
@click="changeCardDoneStatus()">
{{ card.done ? t('deck', 'Mark as not done') : t('deck', 'Mark as done') }}
</NcActionButton>
<NcActionButton v-if="canEdit"
icon="icon-external"
:close-after-click="true"
@click="openCardMoveDialog">
{{ t('deck', 'Move/copy card') }}
</NcActionButton>
<NcActionButton v-for="action in cardActions"
:key="action.label"
:close-after-click="true"
:icon="action.icon"
@click="action.callback(cardRichObject)">
{{ action.label }}
</NcActionButton>
<NcActionButton v-if="canEditBoard" :close-after-click="true" @click="archiveUnarchiveCard()">
<template #icon>
<ArchiveIcon :size="20" decorative />
</template>
{{ card.archived ? t('deck', 'Unarchive card') : t('deck', 'Archive card') }}
</NcActionButton>
<NcActionButton v-if="canEdit"
icon="icon-delete"
:close-after-click="true"
@click="deleteCard()">
{{ t('deck', 'Delete card') }}
</NcActionButton>
</NcActions> </NcActions>
</div> </div>
</template> </template>
<script> <script>
import { NcActions, NcButton } from '@nextcloud/vue' import { NcActions, NcButton, NcActionButton } from '@nextcloud/vue'
import LinkIcon from 'vue-material-design-icons/Link.vue' import LinkIcon from 'vue-material-design-icons/Link.vue'
import CardMenuEntries from './CardMenuEntries.vue' import ArchiveIcon from 'vue-material-design-icons/Archive.vue'
import { getCurrentUser } from '@nextcloud/auth'
import CardBulletedIcon from 'vue-material-design-icons/CardBulleted.vue'
import PencilIcon from 'vue-material-design-icons/Pencil.vue'
import { mapGetters, mapState } from 'vuex'
import { showUndo } from '@nextcloud/dialogs'
import { generateUrl } from '@nextcloud/router'
import '@nextcloud/dialogs/style.css'
import { emit } from '@nextcloud/event-bus'
export default { export default {
name: 'CardMenu', name: 'CardMenu',
components: { NcActions, NcButton, LinkIcon, CardMenuEntries }, components: { NcActions, NcButton, LinkIcon, NcActionButton, PencilIcon, CardBulletedIcon, ArchiveIcon },
props: { props: {
card: { card: {
type: Object, type: Object,
default: null, default: null,
}, },
hideDetailsEntry: {
type: Boolean,
default: false,
},
}, },
emits: ['edit-title'], emits: ['edit-title'],
computed: {
...mapGetters([
'isArchived',
'boards',
'cardActions',
'stackById',
'boardById',
]),
...mapState({
showArchived: state => state.showArchived,
currentBoard: state => state.currentBoard,
}),
canEdit() {
return !this.card.archived
},
canEditBoard() {
if (this.currentBoard) {
return this.$store.getters.canEdit
}
const board = this.$store.getters.boards.find((item) => item.id === this.card.boardId)
return !!board?.permissions?.PERMISSION_EDIT
},
isCurrentUserAssigned() {
return this.card.assignedUsers.find((item) => item.type === 0 && item.participant.uid === getCurrentUser()?.uid)
},
boardId() {
return this.card?.boardId ? this.card.boardId : Number(this.$route.params.id)
},
cardRichObject() {
return {
id: '' + this.card.id,
name: this.card.title,
boardname: this.boardById(this.boardId)?.title,
stackname: this.stackById(this.card.stackId)?.title,
link: window.location.protocol + '//' + window.location.host + generateUrl('/apps/deck/') + `card/${this.card.id}`,
}
},
},
methods: { methods: {
openLink() { openLink() {
window.open(this.card?.referenceData?.openGraphObject?.link) window.open(this.card?.referenceData?.openGraphObject?.link)
return false return false
}, },
editTitle(id) { openCard() {
this.$emit('edit-title', id) const boardId = this.card?.boardId ? this.card.boardId : this.$route?.params.id ?? this.currentBoard.id
if (this.$router) {
this.$router?.push({ name: 'card', params: { id: boardId, cardId: this.card.id } }).catch(() => { })
return
}
this.$root.$emit('open-card', this.card.id)
},
editTitle() {
this.$emit('edit-title', this.card.id)
},
deleteCard() {
this.$store.dispatch('deleteCard', this.card)
const undoCard = { ...this.card, deletedAt: 0 }
showUndo(t('deck', 'Card deleted'), () => this.$store.dispatch('cardUndoDelete', undoCard))
if (this.$router.currentRoute.name === 'card') {
this.$router.push({ name: 'board' })
}
},
changeCardDoneStatus() {
this.$store.dispatch('changeCardDoneStatus', { ...this.card, done: !this.card.done })
},
archiveUnarchiveCard() {
this.$store.dispatch('archiveUnarchiveCard', { ...this.card, archived: !this.card.archived })
},
assignCardToMe() {
this.$store.dispatch('assignCardToUser', {
card: this.card,
assignee: {
userId: getCurrentUser()?.uid,
type: 0,
},
})
},
unassignCardFromMe() {
this.$store.dispatch('removeUserFromCard', {
card: this.card,
assignee: {
userId: getCurrentUser()?.uid,
type: 0,
},
})
},
openCardMoveDialog() {
emit('deck:card:show-move-dialog', this.card)
}, },
}, },
} }

View File

@@ -1,187 +0,0 @@
<!--
- SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<div>
<NcActionButton v-if="!hideDetailsEntry" :close-after-click="true" @click="openCard">
<CardBulletedIcon slot="icon" :size="20" decorative />
{{ t('deck', 'Card details') }}
</NcActionButton>
<NcActionButton v-if="canEdit" :close-after-click="true" @click="editTitle">
<template #icon>
<PencilIcon :size="20" decorative />
</template>
{{ t('deck', 'Edit title') }}
</NcActionButton>
<NcActionButton v-if="canEdit && !isCurrentUserAssigned"
icon="icon-user"
:close-after-click="true"
@click="assignCardToMe()">
{{ t('deck', 'Assign to me') }}
</NcActionButton>
<NcActionButton v-if="canEdit && isCurrentUserAssigned"
icon="icon-user"
:close-after-click="true"
@click="unassignCardFromMe()">
{{ t('deck', 'Unassign myself') }}
</NcActionButton>
<NcActionButton v-if="canEdit"
icon="icon-checkmark"
:close-after-click="true"
@click="changeCardDoneStatus()">
{{ card.done ? t('deck', 'Mark as not done') : t('deck', 'Mark as done') }}
</NcActionButton>
<NcActionButton v-if="canEdit"
icon="icon-external"
:close-after-click="true"
@click="openCardMoveDialog">
{{ t('deck', 'Move/copy card') }}
</NcActionButton>
<NcActionButton v-for="action in cardActions"
:key="action.label"
:close-after-click="true"
:icon="action.icon"
@click="action.callback(cardRichObject)">
{{ action.label }}
</NcActionButton>
<NcActionButton v-if="canEditBoard" :close-after-click="true" @click="archiveUnarchiveCard()">
<template #icon>
<ArchiveIcon :size="20" decorative />
</template>
{{ card.archived ? t('deck', 'Unarchive card') : t('deck', 'Archive card') }}
</NcActionButton>
<NcActionButton v-if="canEdit"
icon="icon-delete"
:close-after-click="true"
@click="deleteCard()">
{{ t('deck', 'Delete card') }}
</NcActionButton>
</div>
</template>
<script>
import { NcActionButton } from '@nextcloud/vue'
import { mapGetters, mapState } from 'vuex'
import ArchiveIcon from 'vue-material-design-icons/Archive.vue'
import CardBulletedIcon from 'vue-material-design-icons/CardBulleted.vue'
import PencilIcon from 'vue-material-design-icons/Pencil.vue'
import { generateUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import { showUndo } from '@nextcloud/dialogs'
import '@nextcloud/dialogs/style.css'
import { emit } from '@nextcloud/event-bus'
export default {
name: 'CardMenuEntries',
components: { NcActionButton, ArchiveIcon, CardBulletedIcon, PencilIcon },
props: {
card: {
type: Object,
default: null,
},
hideDetailsEntry: {
type: Boolean,
default: false,
},
},
emits: ['edit-title'],
data() {
return {
modalShow: false,
selectedBoard: '',
selectedStack: '',
stacksFromBoard: [],
}
},
computed: {
...mapGetters([
'isArchived',
'boards',
'cardActions',
'stackById',
'boardById',
]),
...mapState({
showArchived: state => state.showArchived,
currentBoard: state => state.currentBoard,
}),
canEdit() {
return !this.card.archived
},
canEditBoard() {
if (this.currentBoard) {
return this.$store.getters.canEdit
}
const board = this.$store.getters.boards.find((item) => item.id === this.card.boardId)
return !!board?.permissions?.PERMISSION_EDIT
},
isCurrentUserAssigned() {
return this.card.assignedUsers.find((item) => item.type === 0 && item.participant.uid === getCurrentUser()?.uid)
},
boardId() {
return this.card?.boardId ? this.card.boardId : Number(this.$route.params.id)
},
cardRichObject() {
return {
id: '' + this.card.id,
name: this.card.title,
boardname: this.boardById(this.boardId)?.title,
stackname: this.stackById(this.card.stackId)?.title,
link: window.location.protocol + '//' + window.location.host + generateUrl('/apps/deck/') + `card/${this.card.id}`,
}
},
},
methods: {
openCard() {
const boardId = this.card?.boardId ? this.card.boardId : this.$route?.params.id ?? this.currentBoard.id
if (this.$router) {
this.$router?.push({ name: 'card', params: { id: boardId, cardId: this.card.id } }).catch(() => {})
return
}
this.$root.$emit('open-card', this.card.id)
},
editTitle() {
this.$emit('edit-title', this.card.id)
},
deleteCard() {
this.$store.dispatch('deleteCard', this.card)
const undoCard = { ...this.card, deletedAt: 0 }
showUndo(t('deck', 'Card deleted'), () => this.$store.dispatch('cardUndoDelete', undoCard))
if (this.$router.currentRoute.name === 'card') {
this.$router.push({ name: 'board' })
}
},
changeCardDoneStatus() {
this.$store.dispatch('changeCardDoneStatus', { ...this.card, done: !this.card.done })
},
archiveUnarchiveCard() {
this.$store.dispatch('archiveUnarchiveCard', { ...this.card, archived: !this.card.archived })
},
assignCardToMe() {
this.$store.dispatch('assignCardToUser', {
card: this.card,
assignee: {
userId: getCurrentUser()?.uid,
type: 0,
},
})
},
unassignCardFromMe() {
this.$store.dispatch('removeUserFromCard', {
card: this.card,
assignee: {
userId: getCurrentUser()?.uid,
type: 0,
},
})
},
openCardMoveDialog() {
emit('deck:card:show-move-dialog', this.card)
},
},
}
</script>

View File

@@ -151,16 +151,13 @@
</template> </template>
<script> <script>
import { NcModal, Tooltip } from '@nextcloud/vue' import { NcModal } from '@nextcloud/vue'
export default { export default {
name: 'HelpModal', name: 'HelpModal',
components: { components: {
NcModal, NcModal,
}, },
directives: {
Tooltip,
},
} }
</script> </script>

View File

@@ -13,12 +13,12 @@
</NcColorPicker> </NcColorPicker>
<form @submit.prevent.stop="createBoard"> <form @submit.prevent.stop="createBoard">
<NcTextField ref="inputField" <NcTextField ref="inputField"
v-model="value"
:disable="loading" :disable="loading"
:value.sync="value"
:placeholder="t('deck', 'Board name')" :placeholder="t('deck', 'Board name')"
type="text" type="text"
required /> required />
<NcButton type="tertiary" <NcButton variant="tertiary"
:disabled="loading" :disabled="loading"
:title="t('deck', 'Cancel edit')" :title="t('deck', 'Cancel edit')"
@click.stop.prevent="cancelEdit"> @click.stop.prevent="cancelEdit">
@@ -26,8 +26,8 @@
<CloseIcon :size="20" /> <CloseIcon :size="20" />
</template> </template>
</NcButton> </NcButton>
<NcButton type="tertiary" <NcButton variant="tertiary"
native-type="submit" type="submit"
:disabled="loading" :disabled="loading"
:title="t('deck', 'Save board')"> :title="t('deck', 'Save board')">
<template #icon> <template #icon>
@@ -77,6 +77,7 @@ export default {
}) })
}, },
async createBoard(e) { async createBoard(e) {
alert('createBoard called')
this.loading = true this.loading = true
const title = this.value.trim() const title = this.value.trim()
await this.$store.dispatch('createBoard', { await this.$store.dispatch('createBoard', {
@@ -88,6 +89,7 @@ export default {
this.color = randomColor() this.color = randomColor()
}, },
cancelEdit(e) { cancelEdit(e) {
alert('cancelEdit called')
this.editing = false this.editing = false
this.color = randomColor() this.color = randomColor()
}, },

View File

@@ -25,7 +25,7 @@
<AccountIcon v-if="board.acl.length > 0" /> <AccountIcon v-if="board.acl.length > 0" />
</template> </template>
<template v-if="!deleted" slot="actions"> <template v-if="!deleted" #actions>
<template v-if="!isDueSubmenuActive"> <template v-if="!isDueSubmenuActive">
<NcActionButton icon="icon-info" <NcActionButton icon="icon-info"
:close-after-click="true" :close-after-click="true"
@@ -129,17 +129,17 @@
:placeholder="t('deck', 'Board name')" :placeholder="t('deck', 'Board name')"
type="text" type="text"
required /> required />
<NcButton type="tertiary" <NcButton variant="tertiary"
:disabled="loading" :disabled="loading"
native-type="submit" type="submit"
:title="t('deck', 'Cancel edit')" :title="t('deck', 'Cancel edit')"
@click.stop.prevent="cancelEdit"> @click.stop.prevent="cancelEdit">
<template #icon> <template #icon>
<CloseIcon :size="20" /> <CloseIcon :size="20" />
</template> </template>
</NcButton> </NcButton>
<NcButton type="tertiary" <NcButton variant="tertiary"
native-type="submit" type="submit"
:disabled="loading" :disabled="loading"
:title="t('deck', 'Save board')"> :title="t('deck', 'Save board')">
<template #icon> <template #icon>
@@ -152,7 +152,7 @@
</template> </template>
<script> <script>
import { NcAppNavigationIconBullet, NcAppNavigationItem, NcColorPicker, NcButton, NcTextField, NcActionButton } from '@nextcloud/vue' import { NcAppNavigationIconBullet, NcAppNavigationItem, NcColorPicker, NcButton, NcTextField, NcActionButton, NcLoadingIcon } from '@nextcloud/vue'
import ClickOutside from 'vue-click-outside' import ClickOutside from 'vue-click-outside'
import ArchiveIcon from 'vue-material-design-icons/Archive.vue' import ArchiveIcon from 'vue-material-design-icons/Archive.vue'
import CloneIcon from 'vue-material-design-icons/ContentDuplicate.vue' import CloneIcon from 'vue-material-design-icons/ContentDuplicate.vue'
@@ -186,6 +186,7 @@ export default {
CheckIcon, CheckIcon,
BoardCloneModal, BoardCloneModal,
BoardExportModal, BoardExportModal,
NcLoadingIcon,
}, },
directives: { directives: {
ClickOutside, ClickOutside,

View File

@@ -31,10 +31,6 @@ export default {
type: String, type: String,
default: '', default: '',
}, },
id: {
type: String,
required: true,
},
text: { text: {
type: String, type: String,
required: true, required: true,

View File

@@ -5,16 +5,16 @@
<template> <template>
<NcDialog :name="t('deck', 'Clone {boardTitle}', {boardTitle: boardTitle})" :show="true" @close="close(false)"> <NcDialog :name="t('deck', 'Clone {boardTitle}', {boardTitle: boardTitle})" :show="true" @close="close(false)">
<div class="modal__content"> <div class="modal__content">
<NcCheckboxRadioSwitch :checked.sync="withCards"> <NcCheckboxRadioSwitch v-model="withCards">
{{ t('deck', 'Clone cards') }} {{ t('deck', 'Clone cards') }}
</NcCheckboxRadioSwitch> </NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch v-if="withCards" :checked.sync="withAssignments"> <NcCheckboxRadioSwitch v-if="withCards" v-model="withAssignments">
{{ t('deck', 'Clone assignments') }} {{ t('deck', 'Clone assignments') }}
</NcCheckboxRadioSwitch> </NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch v-if="withCards" :checked.sync="withLabels"> <NcCheckboxRadioSwitch v-if="withCards" v-model="withLabels">
{{ t('deck', 'Clone labels') }} {{ t('deck', 'Clone labels') }}
</NcCheckboxRadioSwitch> </NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch v-if="withCards" :checked.sync="withDueDate"> <NcCheckboxRadioSwitch v-if="withCards" v-model="withDueDate">
{{ t('deck', 'Clone due dates') }} {{ t('deck', 'Clone due dates') }}
</NcCheckboxRadioSwitch> </NcCheckboxRadioSwitch>
<div v-if="withCards" class="accordion" :class="{ 'is-open': accordionOpen }"> <div v-if="withCards" class="accordion" :class="{ 'is-open': accordionOpen }">
@@ -25,10 +25,10 @@
{{ t('deck', 'Advanced options') }} {{ t('deck', 'Advanced options') }}
</div> </div>
<div v-if="accordionOpen" class="accordion__content"> <div v-if="accordionOpen" class="accordion__content">
<NcCheckboxRadioSwitch v-if="withCards" :checked.sync="moveCardsToLeftStack"> <NcCheckboxRadioSwitch v-if="withCards" v-model="moveCardsToLeftStack">
{{ t('deck', 'Move all cards to the first list') }} {{ t('deck', 'Move all cards to the first list') }}
</NcCheckboxRadioSwitch> </NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch v-if="withCards" :checked.sync="restoreArchivedCards"> <NcCheckboxRadioSwitch v-if="withCards" v-model="restoreArchivedCards">
{{ t('deck', 'Restore archived cards') }} {{ t('deck', 'Restore archived cards') }}
</NcCheckboxRadioSwitch> </NcCheckboxRadioSwitch>
</div> </div>
@@ -39,7 +39,7 @@
<NcButton @click="cancel"> <NcButton @click="cancel">
{{ t('deck', 'Cancel') }} {{ t('deck', 'Cancel') }}
</NcButton> </NcButton>
<NcButton type="primary" @click="save"> <NcButton variant="primary" @click="save">
{{ t('deck', 'Clone') }} {{ t('deck', 'Clone') }}
</NcButton> </NcButton>
</template> </template>

View File

@@ -5,13 +5,13 @@
<template> <template>
<NcDialog :name="t('deck', 'Export {boardTitle}', {boardTitle: boardTitle})" @update:open="close"> <NcDialog :name="t('deck', 'Export {boardTitle}', {boardTitle: boardTitle})" @update:open="close">
<div class="modal__content"> <div class="modal__content">
<NcCheckboxRadioSwitch :checked.sync="exportFormat" <NcCheckboxRadioSwitch v-model="exportFormat"
value="json" value="json"
type="radio" type="radio"
name="board_export_format"> name="board_export_format">
{{ t('deck', 'Export as JSON') }} {{ t('deck', 'Export as JSON') }}
</NcCheckboxRadioSwitch> </NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch :checked.sync="exportFormat" <NcCheckboxRadioSwitch v-model="exportFormat"
value="csv" value="csv"
type="radio" type="radio"
name="board_export_format"> name="board_export_format">
@@ -27,7 +27,7 @@
<NcButton @click="close"> <NcButton @click="close">
{{ t('deck', 'Cancel') }} {{ t('deck', 'Cancel') }}
</NcButton> </NcButton>
<NcButton type="primary" @click="exportBoard"> <NcButton variant="primary" @click="exportBoard">
{{ t('deck', 'Export') }} {{ t('deck', 'Export') }}
</NcButton> </NcButton>
</template> </template>

View File

@@ -145,7 +145,7 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import './../../css/variables'; @use './../../css/variables';
.overview-wrapper { .overview-wrapper {
position: relative; position: relative;
@@ -162,16 +162,16 @@ export default {
overflow-x: scroll; overflow-x: scroll;
display: flex; display: flex;
align-items: stretch; align-items: stretch;
padding-left: $board-spacing; padding-left: variables.$board-spacing;
padding-right: $board-spacing; padding-right: variables.$board-spacing;
.dashboard-column { .dashboard-column {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: $stack-width; min-width: variables.$stack-width;
width: $stack-width; width: variables.$stack-width;
margin-left: $stack-spacing; margin-left: variables.$stack-spacing;
margin-right: $stack-spacing; margin-right: variables.$stack-spacing;
h3 { h3 {
font-size: var(--default-font-size); font-size: var(--default-font-size);

View File

@@ -4,7 +4,7 @@
--> -->
<template> <template>
<div v-if="searchQuery!==''" class="global-search"> <div v-if="searchQuery !== ''" class="global-search">
<h2> <h2>
<NcRichText :text="t('deck', 'Search for {searchQuery} in all boards')" :arguments="queryStringArgs" /> <NcRichText :text="t('deck', 'Search for {searchQuery} in all boards')" :arguments="queryStringArgs" />
<div v-if="loading" class="icon-loading-small" /> <div v-if="loading" class="icon-loading-small" />
@@ -19,13 +19,13 @@
:key="card.id" :key="card.id"
:standalone="true" /> :standalone="true" />
<Placeholder v-if="loading" /> <Placeholder v-if="loading" />
<InfiniteLoading :identifier="searchQuery" @infinite="infiniteHandler"> <!-- <InfiniteLoading :identifier="searchQuery" @infinite="infiniteHandler">
<div slot="spinner" /> <div slot="spinner" />
<div slot="no-more" /> <div slot="no-more" />
<div slot="no-results"> <div slot="no-results">
{{ t('deck', 'No results found') }} {{ t('deck', 'No results found') }}
</div> </div>
</InfiniteLoading> </InfiniteLoading> -->
</div> </div>
<div v-else> <div v-else>
<p>{{ t('deck', 'No results found') }}</p> <p>{{ t('deck', 'No results found') }}</p>
@@ -39,7 +39,7 @@ import CardItem from '../cards/CardItem.vue'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router' import { generateOcsUrl } from '@nextcloud/router'
import InfiniteLoading from 'vue-infinite-loading' // import InfiniteLoading from 'v3-infinite-loading'
import Placeholder from './Placeholder.vue' import Placeholder from './Placeholder.vue'
import { NcActions, NcActionButton, NcRichText } from '@nextcloud/vue' import { NcActions, NcActionButton, NcRichText } from '@nextcloud/vue'
@@ -70,7 +70,14 @@ function search({ query, cursor }) {
export default { export default {
name: 'GlobalSearchResults', name: 'GlobalSearchResults',
components: { CardItem, InfiniteLoading, NcRichText, Placeholder, NcActions, NcActionButton }, components: {
CardItem,
// InfiniteLoading,
NcRichText,
Placeholder,
NcActions,
NcActionButton,
},
data() { data() {
return { return {
results: [], results: [],
@@ -155,11 +162,11 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../css/variables'; @use '../../css/variables';
.global-search { .global-search {
width: 100%; width: 100%;
padding: $board-spacing + $stack-spacing; padding: variables.$board-spacing + variables.$stack-spacing;
padding-bottom: 0; padding-bottom: 0;
overflow: hidden; overflow: hidden;
min-height: 35vh; min-height: 35vh;
@@ -175,6 +182,7 @@ export default {
top: 10px; top: 10px;
right: 10px; right: 10px;
} }
.search-wrapper { .search-wrapper {
overflow: scroll; overflow: scroll;
height: 100%; height: 100%;
@@ -182,13 +190,14 @@ export default {
padding: 10px; padding: 10px;
} }
h2 > div { h2>div {
display: inline-block; display: inline-block;
&.icon-loading-small { &.icon-loading-small {
margin-right: 20px; margin-right: 20px;
} }
} }
h2:deep(span) { h2:deep(span) {
background-color: var(--color-background-dark); background-color: var(--color-background-dark);
padding: 3px; padding: 3px;
@@ -199,13 +208,14 @@ export default {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
& > div { &>div {
flex-grow: 0; flex-grow: 0;
} }
} }
&:deep(.card) { &:deep(.card) {
width: $stack-width; width: variables.$stack-width;
margin-right: $stack-spacing; margin-right: variables.$stack-spacing;
} }
} }
</style> </style>

View File

@@ -58,18 +58,18 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../css/variables'; @use '../../css/variables';
$clickable-area: var(--default-clickable-area); $clickable-area: var(--default-clickable-area);
.card--placeholder { .card--placeholder {
width: $stack-width; width: variables.$stack-width;
margin-right: $stack-spacing; margin-right: variables.$stack-spacing;
padding: $card-padding; padding: variables.$card-padding;
transition: box-shadow 0.1s ease-in-out; transition: box-shadow 0.1s ease-in-out;
box-shadow: 0 0 2px 0 var(--color-box-shadow); box-shadow: 0 0 2px 0 var(--color-box-shadow);
border-radius: var(--border-radius-large); border-radius: var(--border-radius-large);
font-size: 100%; font-size: 100%;
margin-bottom: $card-spacing; margin-bottom: variables.$card-spacing;
height: 100px; height: 100px;
} }

View File

@@ -3,38 +3,34 @@
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */
import Vue from 'vue'
import './../css/collections.css' import './../css/collections.css'
import FileSharingPicker from './views/FileSharingPicker.js' import FileSharingPicker from './views/FileSharingPicker.js'
import { buildSelector } from './helpers/selector.js' import { buildSelector } from './helpers/selector.js'
import './shared-init.js' import './shared-init.js'
Vue.prototype.t = t export function initCollections() {
Vue.prototype.n = n window.addEventListener('DOMContentLoaded', () => {
Vue.prototype.OC = OC if (OCA.Sharing && OCA.Sharing.ShareSearch) {
OCA.Sharing.ShareSearch.addNewResult(FileSharingPicker)
}
window.addEventListener('DOMContentLoaded', () => { window.OCP.Collaboration.registerType('deck', {
if (OCA.Sharing && OCA.Sharing.ShareSearch) { action: () => {
OCA.Sharing.ShareSearch.addNewResult(FileSharingPicker) const BoardSelector = () => import('./BoardSelector.vue')
} return buildSelector(BoardSelector)
},
typeString: t('deck', 'Link to a board'),
typeIconClass: 'icon-deck',
})
window.OCP.Collaboration.registerType('deck', { window.OCP.Collaboration.registerType('deck-card', {
action: () => { action: () => {
const BoardSelector = () => import('./BoardSelector.vue') const CardSelector = () => import('./CardSelector.vue')
return buildSelector(BoardSelector) return buildSelector(CardSelector)
}, },
typeString: t('deck', 'Link to a board'), typeString: t('deck', 'Link to a card'),
typeIconClass: 'icon-deck', typeIconClass: 'icon-deck',
})
}) })
}
window.OCP.Collaboration.registerType('deck-card', {
action: () => {
const CardSelector = () => import('./CardSelector.vue')
return buildSelector(CardSelector)
},
typeString: t('deck', 'Link to a card'),
typeIconClass: 'icon-deck',
})
})

View File

@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */
import { registerWidget, registerCustomPickerElement, NcCustomPickerRenderResult } from '@nextcloud/vue/dist/Functions/registerReference.js' import { registerWidget, registerCustomPickerElement, NcCustomPickerRenderResult } from '@nextcloud/vue'
import { translate, translatePlural } from '@nextcloud/l10n' import { translate, translatePlural } from '@nextcloud/l10n'
import './shared-init.js' import './shared-init.js'

View File

@@ -2,11 +2,10 @@
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */
import Vue from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import router from './router.js' import router from './router.js'
import store from './store/main.js' import store from './store/main.js'
import { sync } from 'vuex-router-sync'
import { translate, translatePlural } from '@nextcloud/l10n' import { translate, translatePlural } from '@nextcloud/l10n'
import { showError } from '@nextcloud/dialogs' import { showError } from '@nextcloud/dialogs'
import { subscribe } from '@nextcloud/event-bus' import { subscribe } from '@nextcloud/event-bus'
@@ -14,27 +13,33 @@ import ClickOutside from 'vue-click-outside'
import './shared-init.js' import './shared-init.js'
import './models/index.js' import './models/index.js'
import './sessions.js' import './sessions.js'
import { initCollections } from './init-collections.js'
// the server snap.js conflicts with vertical scrolling so we disable it // the server snap.js conflicts with vertical scrolling so we disable it
document.body.setAttribute('data-snap-ignore', 'true') document.body.setAttribute('data-snap-ignore', 'true')
sync(store, router) const app = createApp(App)
Vue.prototype.t = translate app.config.globalProperties.t = translate
Vue.prototype.n = translatePlural app.config.globalProperties.n = translatePlural
app.config.globalProperties.OC = OC
Vue.directive('click-outside', ClickOutside) initCollections({ t, n, OC })
Vue.directive('focus', { app.directive('click-outside', ClickOutside)
inserted(el) {
app.directive('focus', {
mounted(el) {
el.focus() el.focus()
}, },
}) })
Vue.config.errorHandler = (err, vm, info) => { app.config.errorHandler = (err, vm, info) => {
if (err.response && err.response.data.message) { if (err.response && err.response.data.message) {
const errorMessage = t('deck', 'Something went wrong') const errorMessage = translate('deck', 'Something went wrong')
showError(`${errorMessage}: ${err.response.data.status} ${err.response.data.message}`) showError(
`${errorMessage}: ${err.response.data.status} ${err.response.data.message}`,
)
} }
console.error(err) console.error(err)
} }
@@ -47,16 +52,14 @@ window.addEventListener('DOMContentLoaded', () => {
window.OCA.Files = {} window.OCA.Files = {}
} }
// register unused client for the sidebar to have access to its parser methods // register unused client for the sidebar to have access to its parser methods
Object.assign(window.OCA.Files, { App: { fileList: { filesClient: OC.Files.getClient() } } }, window.OCA.Files) Object.assign(
window.OCA.Files,
{ App: { fileList: { filesClient: OC.Files.getClient() } } },
window.OCA.Files,
)
}) })
/* eslint-disable-next-line no-new */ app.mixin({
new Vue({
el: '#content',
// eslint-disable-next-line vue/match-component-file-name
name: 'Deck',
router,
store,
data() { data() {
return { return {
time: Date.now(), time: Date.now(),
@@ -75,7 +78,7 @@ new Vue({
this.time = Date.now() this.time = Date.now()
}, 1000) }, 1000)
}, },
beforeDestroy() { beforeUnmount() {
clearInterval(this.interval) clearInterval(this.interval)
}, },
methods: { methods: {
@@ -86,9 +89,12 @@ new Vue({
this.$store.commit('setSearchQuery', '') this.$store.commit('setSearchQuery', '')
}, },
}, },
render: h => h(App),
}) })
app.use(router)
app.use(store)
app.mount('#content')
if (!window.OCA.Deck) { if (!window.OCA.Deck) {
window.OCA.Deck = {} window.OCA.Deck = {}
} }

View File

@@ -26,7 +26,8 @@ export default {
return return
} }
this.$set(this.uploadQueue, file.name, { name: file.name, progress: 0 }) this.uploadQueue[file.name] = { name: file.name, progress: 0 }
const bodyFormData = new FormData() const bodyFormData = new FormData()
bodyFormData.append('cardId', this.cardId) bodyFormData.append('cardId', this.cardId)
bodyFormData.append('type', type) bodyFormData.append('type', type)
@@ -39,7 +40,7 @@ export default {
onUploadProgress: (e) => { onUploadProgress: (e) => {
const percentCompleted = Math.round((e.loaded * 100) / e.total) const percentCompleted = Math.round((e.loaded * 100) / e.total)
console.debug(percentCompleted) console.debug(percentCompleted)
this.$set(this.uploadQueue[file.name], 'progress', percentCompleted) this.uploadQueue[file.name].progress = percentCompleted
}, },
}) })
} catch (err) { } catch (err) {
@@ -50,7 +51,7 @@ export default {
showError(err.response.data ? err.response.data.message : 'Failed to upload file') showError(err.response.data ? err.response.data.message : 'Failed to upload file')
} }
} }
this.$delete(this.uploadQueue, file.name) delete this.uploadQueue[file.name]
}) })
}, },

View File

@@ -3,8 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */
import Vue from 'vue' import { createRouter, createWebHistory } from 'vue-router'
import Router from 'vue-router'
import { generateUrl, getRootUrl } from '@nextcloud/router' import { generateUrl, getRootUrl } from '@nextcloud/router'
import { BOARD_FILTERS } from './store/main.js' import { BOARD_FILTERS } from './store/main.js'
import Boards from './components/boards/Boards.vue' import Boards from './components/boards/Boards.vue'
@@ -14,17 +13,14 @@ import BoardSidebar from './components/board/BoardSidebar.vue'
import CardSidebar from './components/card/CardSidebar.vue' import CardSidebar from './components/card/CardSidebar.vue'
import Overview from './components/overview/Overview.vue' import Overview from './components/overview/Overview.vue'
Vue.use(Router)
// We apply a dynamic base URL depending on the URL used in the browser // We apply a dynamic base URL depending on the URL used in the browser
const baseUrl = generateUrl('/apps/deck/') const baseUrl = generateUrl('/apps/deck/')
const webRootWithIndexPHP = getRootUrl() + '/index.php' const webRootWithIndexPHP = getRootUrl() + '/index.php'
const doesURLContainIndexPHP = window.location.pathname.startsWith(webRootWithIndexPHP) const doesURLContainIndexPHP = window.location.pathname.startsWith(webRootWithIndexPHP)
const currentBaseUrl = doesURLContainIndexPHP ? baseUrl : baseUrl.replace('/index.php/', '/') const currentBaseUrl = doesURLContainIndexPHP ? baseUrl : baseUrl.replace('/index.php/', '/')
const router = new Router({ const router = createRouter({
mode: 'history', history: createWebHistory(currentBaseUrl),
base: currentBaseUrl,
linkActiveClass: 'active', linkActiveClass: 'active',
routes: [ routes: [
{ {

View File

@@ -4,7 +4,6 @@
*/ */
import { AttachmentApi } from './../services/AttachmentApi.js' import { AttachmentApi } from './../services/AttachmentApi.js'
import Vue from 'vue'
const apiClient = new AttachmentApi() const apiClient = new AttachmentApi()
@@ -24,20 +23,20 @@ export default {
mutations: { mutations: {
createAttachment(state, { cardId, attachment }) { createAttachment(state, { cardId, attachment }) {
if (typeof state.attachments[cardId] === 'undefined') { if (typeof state.attachments[cardId] === 'undefined') {
Vue.set(state.attachments, cardId, [attachment]) state.attachments[cardId] = [attachment]
} else { } else {
state.attachments[cardId].push(attachment) state.attachments[cardId].push(attachment)
} }
}, },
createAttachments(state, { cardId, attachments }) { createAttachments(state, { cardId, attachments }) {
Vue.set(state.attachments, cardId, attachments) state.attachments[cardId] = attachments
}, },
updateAttachment(state, { cardId, attachment }) { updateAttachment(state, { cardId, attachment }) {
const existingIndex = state.attachments[attachment.cardId].findIndex(a => a.id === attachment.id && a.type === attachment.type) const existingIndex = state.attachments[attachment.cardId].findIndex(a => a.id === attachment.id && a.type === attachment.type)
if (existingIndex !== -1) { if (existingIndex !== -1) {
Vue.set(state.attachments[cardId], existingIndex, attachment) state.attachments[cardId][existingIndex] = attachment
} }
}, },

View File

@@ -5,7 +5,6 @@
import { CardApi } from './../services/CardApi.js' import { CardApi } from './../services/CardApi.js'
import moment from 'moment' import moment from 'moment'
import Vue from 'vue'
const apiClient = new CardApi() const apiClient = new CardApi()
@@ -190,7 +189,7 @@ export default {
const existingIndex = state.cards.findIndex(_card => _card.id === card.id) const existingIndex = state.cards.findIndex(_card => _card.id === card.id)
if (existingIndex !== -1) { if (existingIndex !== -1) {
const existingCard = state.cards.find(_card => _card.id === card.id) const existingCard = state.cards.find(_card => _card.id === card.id)
Vue.set(state.cards, existingIndex, Object.assign({}, existingCard, card)) state.cards[existingIndex] = { ...existingCard, ...card }
} else { } else {
state.cards.push(card) state.cards.push(card)
} }
@@ -204,15 +203,15 @@ export default {
updateCard(state, card) { updateCard(state, card) {
const existingIndex = state.cards.findIndex(_card => _card.id === card.id) const existingIndex = state.cards.findIndex(_card => _card.id === card.id)
if (existingIndex !== -1) { if (existingIndex !== -1) {
Vue.set(state.cards, existingIndex, Object.assign({}, state.cards[existingIndex], card)) state.cards[existingIndex] = { ...state.cards[existingIndex], ...card }
} }
}, },
updateCardsReorder(state, cards) { updateCardsReorder(state, cards) {
for (const newCard of cards) { for (const newCard of cards) {
const existingIndex = state.cards.findIndex(_card => _card.id === newCard.id) const existingIndex = state.cards.findIndex(_card => _card.id === newCard.id)
if (existingIndex !== -1) { if (existingIndex !== -1) {
Vue.set(state.cards[existingIndex], 'order', newCard.order) state.cards[existingIndex].order = newCard.order
Vue.set(state.cards[existingIndex], 'stackId', newCard.stackId) state.cards[existingIndex].stackId = newCard.stackId
} }
} }
}, },
@@ -234,26 +233,26 @@ export default {
updateCardProperty(state, { card, property }) { updateCardProperty(state, { card, property }) {
const existingIndex = state.cards.findIndex(_card => _card.id === card.id) const existingIndex = state.cards.findIndex(_card => _card.id === card.id)
if (existingIndex !== -1) { if (existingIndex !== -1) {
Vue.set(state.cards[existingIndex], property, card[property]) state.cards[existingIndex][property] = card[property]
Vue.set(state.cards[existingIndex], 'lastModified', Date.now() / 1000) state.cards[existingIndex].lastModifiedBy = Date.now() / 1000
} }
}, },
cardSetAttachmentCount(state, { cardId, count }) { cardSetAttachmentCount(state, { cardId, count }) {
const existingIndex = state.cards.findIndex(_card => _card.id === cardId) const existingIndex = state.cards.findIndex(_card => _card.id === cardId)
if (existingIndex !== -1) { if (existingIndex !== -1) {
Vue.set(state.cards[existingIndex], 'attachmentCount', count) state.cards[existingIndex].attachmentCount = count
} }
}, },
cardIncreaseAttachmentCount(state, cardId) { cardIncreaseAttachmentCount(state, cardId) {
const existingIndex = state.cards.findIndex(_card => _card.id === cardId) const existingIndex = state.cards.findIndex(_card => _card.id === cardId)
if (existingIndex !== -1) { if (existingIndex !== -1) {
Vue.set(state.cards[existingIndex], 'attachmentCount', state.cards[existingIndex].attachmentCount + 1) state.cards[existingIndex].attachmentCount = state.cards[existingIndex].attachmentCount + 1
} }
}, },
cardDecreaseAttachmentCount(state, cardId) { cardDecreaseAttachmentCount(state, cardId) {
const existingIndex = state.cards.findIndex(_card => _card.id === cardId) const existingIndex = state.cards.findIndex(_card => _card.id === cardId)
if (existingIndex !== -1) { if (existingIndex !== -1) {
Vue.set(state.cards[existingIndex], 'attachmentCount', state.cards[existingIndex].attachmentCount - 1) state.cards[existingIndex].attachmentCount = state.cards[existingIndex].attachmentCount - 1
} }
}, },
addNewCard(state, card) { addNewCard(state, card) {

View File

@@ -4,7 +4,6 @@
*/ */
import { CommentApi } from '../services/CommentApi.js' import { CommentApi } from '../services/CommentApi.js'
import Vue from 'vue'
const apiClient = new CommentApi() const apiClient = new CommentApi()
@@ -34,10 +33,10 @@ export default {
}, },
addComments(state, { comments, cardId }) { addComments(state, { comments, cardId }) {
if (state.comments[cardId] === undefined) { if (state.comments[cardId] === undefined) {
Vue.set(state.comments, cardId, { state.comments[cardId] = {
hasMore: comments.length > 0, hasMore: comments.length >= 0,
comments: [...comments], comments: [...comments],
}) }
} else { } else {
const newComments = comments.filter((comment) => { const newComments = comments.filter((comment) => {
return state.comments[cardId].comments.findIndex((item) => item.id === comment.id) === -1 return state.comments[cardId].comments.findIndex((item) => item.id === comment.id) === -1
@@ -59,11 +58,11 @@ export default {
}, },
markCommentsAsRead(state, cardId) { markCommentsAsRead(state, cardId) {
state.comments[cardId].comments.forEach(_comment => { state.comments[cardId].comments.forEach(_comment => {
Vue.set(_comment, 'isUnread', false) _comment.isUnread = false
}) })
}, },
setReplyTo(state, comment) { setReplyTo(state, comment) {
Vue.set(state, 'replyTo', comment) state.replyTo = comment
}, },
}, },
actions: { actions: {

View File

@@ -6,8 +6,7 @@
import 'url-search-params-polyfill' import 'url-search-params-polyfill'
import { loadState } from '@nextcloud/initial-state' import { loadState } from '@nextcloud/initial-state'
import Vue from 'vue' import { createStore } from 'vuex'
import Vuex from 'vuex'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { generateOcsUrl, generateUrl } from '@nextcloud/router' import { generateOcsUrl, generateUrl } from '@nextcloud/router'
import { BoardApi } from '../services/BoardApi.js' import { BoardApi } from '../services/BoardApi.js'
@@ -18,7 +17,6 @@ import comment from './comment.js'
import trashbin from './trashbin.js' import trashbin from './trashbin.js'
import attachment from './attachment.js' import attachment from './attachment.js'
import overview from './overview.js' import overview from './overview.js'
Vue.use(Vuex)
const apiClient = new BoardApi() const apiClient = new BoardApi()
const debug = process.env.NODE_ENV !== 'production' const debug = process.env.NODE_ENV !== 'production'
@@ -29,7 +27,7 @@ export const BOARD_FILTERS = {
SHARED: 'shared', SHARED: 'shared',
} }
export default new Vuex.Store({ const store = createStore({
modules: { modules: {
actions, actions,
stack, stack,
@@ -130,10 +128,10 @@ export default new Vuex.Store({
}, },
mutations: { mutations: {
setFullApp(state, isFullApp) { setFullApp(state, isFullApp) {
Vue.set(state, 'isFullApp', isFullApp) state.isFullApp = isFullApp
}, },
setHasCardSaveError(state, hasCardSaveError) { setHasCardSaveError(state, hasCardSaveError) {
Vue.set(state, 'hasCardSaveError', hasCardSaveError) state.hasCardSaveError = hasCardSaveError
}, },
SET_CONFIG(state, { key, value }) { SET_CONFIG(state, { key, value }) {
const [scope, id, configKey] = key.split(':', 3) const [scope, id, configKey] = key.split(':', 3)
@@ -145,11 +143,11 @@ export default new Vuex.Store({
}) })
if (indexExisting > -1) { if (indexExisting > -1) {
Vue.set(state.boards[indexExisting].settings, configKey, value) state.boards[indexExisting].settings[configKey] = value
} }
break break
default: default:
Vue.set(state.config, key, value) state.config[key] = value
} }
}, },
setSearchQuery(state, searchQuery) { setSearchQuery(state, searchQuery) {
@@ -162,7 +160,7 @@ export default new Vuex.Store({
Object.keys(filter).forEach((key) => { Object.keys(filter).forEach((key) => {
switch (key) { switch (key) {
case 'due': case 'due':
Vue.set(state.filter, key, filter.due) state.filter[key] = filter.due
break break
default: default:
filter[key].forEach((item) => { filter[key].forEach((item) => {
@@ -189,7 +187,7 @@ export default new Vuex.Store({
}) })
if (indexExisting > -1) { if (indexExisting > -1) {
Vue.set(state.boards, indexExisting, board) state.boards[indexExisting] = board
} else { } else {
state.boards.push(board) state.boards.push(board)
} }
@@ -201,7 +199,7 @@ export default new Vuex.Store({
}) })
if (indexExisting > -1) { if (indexExisting > -1) {
Vue.set(state.boards, indexExisting, board) state.boards[indexExisting] = board
} else { } else {
state.boards.push(board) state.boards.push(board)
} }
@@ -234,7 +232,7 @@ export default new Vuex.Store({
state.boards = boards state.boards = boards
}, },
setSharees(state, shareesUsersAndGroups) { setSharees(state, shareesUsersAndGroups) {
Vue.set(state, 'sharees', shareesUsersAndGroups.exact.users) state.sharees = shareesUsersAndGroups.exact.users
state.sharees.push(...shareesUsersAndGroups.exact.groups) state.sharees.push(...shareesUsersAndGroups.exact.groups)
state.sharees.push(...shareesUsersAndGroups.exact.circles) state.sharees.push(...shareesUsersAndGroups.exact.circles)
@@ -284,7 +282,7 @@ export default new Vuex.Store({
updateAclFromCurrentBoard(state, acl) { updateAclFromCurrentBoard(state, acl) {
for (const acl_ in state.currentBoard.acl) { for (const acl_ in state.currentBoard.acl) {
if (state.currentBoard.acl[acl_].participant.uid === acl.participant.uid) { if (state.currentBoard.acl[acl_].participant.uid === acl.participant.uid) {
Vue.set(state.currentBoard.acl, acl_, acl) state.currentBoard.acl[acl_] = acl
break break
} }
} }
@@ -300,7 +298,7 @@ export default new Vuex.Store({
} }
if (removeIndex > -1) { if (removeIndex > -1) {
Vue.delete(state.currentBoard.acl, removeIndex) state.currentBoard.acl.splice(removeIndex, 1)
} }
}, },
TOGGLE_SHORTCUT_LOCK(state, lock) { TOGGLE_SHORTCUT_LOCK(state, lock) {
@@ -531,3 +529,5 @@ export default new Vuex.Store({
}, },
}, },
}) })
export default store

View File

@@ -3,10 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */
import Vue from 'vue'
import Vuex from 'vuex'
import { OverviewApi } from '../services/OverviewApi.js' import { OverviewApi } from '../services/OverviewApi.js'
Vue.use(Vuex)
const apiClient = new OverviewApi() const apiClient = new OverviewApi()
export default { export default {

View File

@@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */
import Vue from 'vue'
import { StackApi } from './../services/StackApi.js' import { StackApi } from './../services/StackApi.js'
import applyOrderToArray from './../helpers/applyOrderToArray.js' import applyOrderToArray from './../helpers/applyOrderToArray.js'
@@ -26,7 +25,7 @@ export default {
const existingIndex = state.stacks.findIndex(_stack => _stack.id === stack.id) const existingIndex = state.stacks.findIndex(_stack => _stack.id === stack.id)
if (existingIndex !== -1) { if (existingIndex !== -1) {
const existingStack = state.stacks.find(_stack => _stack.id === stack.id) const existingStack = state.stacks.find(_stack => _stack.id === stack.id)
Vue.set(state.stacks, existingIndex, Object.assign({}, existingStack, stack)) state.stacks[existingIndex] = { ...existingStack, ...stack }
} else { } else {
state.stacks.push(stack) state.stacks.push(stack)
} }

View File

@@ -33,7 +33,7 @@ import DeckIcon from '../components/icons/DeckIcon.vue'
import { BoardApi } from './../services/BoardApi.js' import { BoardApi } from './../services/BoardApi.js'
import store from './../store/main.js' import store from './../store/main.js'
import NcUserBubble from '@nextcloud/vue/dist/Components/NcUserBubble.js' import { NcUserBubble } from '@nextcloud/vue'
import moment from '@nextcloud/moment' import moment from '@nextcloud/moment'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'

View File

@@ -82,7 +82,7 @@
{{ t('deck', 'Cancel') }} {{ t('deck', 'Cancel') }}
</NcButton> </NcButton>
<NcButton :disabled="loading || !isBoardAndStackChoosen" <NcButton :disabled="loading || !isBoardAndStackChoosen"
type="primary" variant="primary"
@click="createCard"> @click="createCard">
{{ t('deck', 'Create card') }} {{ t('deck', 'Create card') }}
</NcButton> </NcButton>
@@ -212,8 +212,8 @@ export default {
}, },
}, },
beforeMount() { beforeMount() {
this.$set(this.card, 'title', this.title) this.card.title = this.title
this.$set(this.card, 'description', this.description) this.card.description = this.description
this.fetchBoards() this.fetchBoards()
}, },
mounted() { mounted() {

View File

@@ -7,7 +7,7 @@ const isDevServer = process.env.WEBPACK_SERVE
webpackConfig.entry = { webpackConfig.entry = {
...webpackConfig.entry, ...webpackConfig.entry,
collections: path.join(__dirname, 'src', 'init-collections.js'), // collections: path.join(__dirname, 'src', 'init-collections.js'),
dashboard: path.join(__dirname, 'src', 'init-dashboard.js'), dashboard: path.join(__dirname, 'src', 'init-dashboard.js'),
calendar: path.join(__dirname, 'src', 'init-calendar.js'), calendar: path.join(__dirname, 'src', 'init-calendar.js'),
talk: path.join(__dirname, 'src', 'init-talk.js'), talk: path.join(__dirname, 'src', 'init-talk.js'),
@@ -22,6 +22,9 @@ if (isDevServer) {
}) })
) )
} else { } else {
webpackConfig.output.clean = {
keep: /\webpack-stats\.json$/,
}
webpackConfig.stats = { webpackConfig.stats = {
context: path.resolve(__dirname, 'src'), context: path.resolve(__dirname, 'src'),
assets: true, assets: true,
@@ -30,7 +33,5 @@ if (isDevServer) {
modules: true, modules: true,
} }
} }
// Workaround for https://github.com/nextcloud/webpack-vue-config/pull/432 causing problems with nextcloud-vue-collections
webpackConfig.resolve.alias = {}
module.exports = webpackConfig module.exports = webpackConfig