Compare commits

...

6 Commits

Author SHA1 Message Date
Julius Härtl
28219c91f3 Bump version to 1.3.0
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-02-20 12:36:23 +01:00
Julius Härtl
f75de55d14 Merge pull request #2817 from nextcloud/backport/2812/stable1.3
[stable1.3] Fix issues when creating a card from a talk message
2021-02-20 11:13:01 +01:00
Julius Härtl
1279e7536c Fix issues when creating a card from a talk message
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-02-20 10:05:58 +00:00
Julius Härtl
0c7da5618c Merge pull request #2810 from nextcloud/backport/2795/stable1.3
[stable1.3] Register talk message action for creating deck cards
2021-02-19 16:08:47 +01:00
Julius Härtl
ec73794b25 Register talk message action for creating deck cards
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-02-19 11:56:38 +00:00
Julius Härtl
2c26e98d3b Keep using ubuntu 18.04 for the app build for now
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-02-16 12:32:42 +01:00
11 changed files with 395 additions and 65 deletions

View File

@@ -13,7 +13,7 @@ env:
jobs:
integration:
runs-on: ubuntu-latest
runs-on: ubuntu-18.04
strategy:
fail-fast: false

View File

@@ -1,24 +1,27 @@
# Changelog
All notable changes to this project will be documented in this file.
## 1.3.0 - unreleased
## 1.3.0 - 2021-02-20
### Added
* [#2638](https://github.com/nextcloud/deck/pull/2638) Sharing files to cards
* [#2683](https://github.com/nextcloud/deck/pull/2683) Handle clicks on calendar entries
* [#2810](https://github.com/nextcloud/deck/pull/2810) Register talk message action for creating deck cards (Requires Nextcloud Talk 11.1.0)
* [#2700](https://github.com/nextcloud/deck/pull/2700) Attempt to copy file on dropping it to deck
* [#2701](https://github.com/nextcloud/deck/pull/2701) Fix uploading files by drag and drop
* [#2707](https://github.com/nextcloud/deck/pull/2707) L10n: Change to a capital letter @Valdnet
* [#2772](https://github.com/nextcloud/deck/pull/2772) Add API to register card actions
* Nextcloud 21 compatiblity
### Fixed
* [#2622](https://github.com/nextcloud/deck/pull/2622) Fix gradient and stack header spacing for safari
* [#2626](https://github.com/nextcloud/deck/pull/2626) Adding a description icon to cards when they contain a description without any checkmarks @MonkeySon
* [#2659](https://github.com/nextcloud/deck/pull/2659) Matching color of description cursor with text color @JonFStr
* [#2676](https://github.com/nextcloud/deck/pull/2676) Only load filter view when shown
* [#2680](https://github.com/nextcloud/deck/pull/2680) Do not try to add change data if it doesn't exist
* [#2681](https://github.com/nextcloud/deck/pull/2681) Filter out deleted stacks from results
* [#2685](https://github.com/nextcloud/deck/pull/2685) Show all boards in move card dialog @jakobroehrl
* [#2687](https://github.com/nextcloud/deck/pull/2687) 3dots no opacity @jakobroehrl
* [#2688](https://github.com/nextcloud/deck/pull/2688) Title > boardname @jakobroehrl
* [#2689](https://github.com/nextcloud/deck/pull/2689) Modal > bigger view wording @jakobroehrl
* [#2712](https://github.com/nextcloud/deck/pull/2712) Docs: Fix table in section "GET /api/v1.0/config" @das-g
* [#2716](https://github.com/nextcloud/deck/pull/2716) Remove repair step which is no longer needed as we cleanup properly
* [#2723](https://github.com/nextcloud/deck/pull/2723) Pad random color with leading zeroes @PVince81
* [#2729](https://github.com/nextcloud/deck/pull/2729) Remove invalid activity parameters @nickvergessen
* [#2750](https://github.com/nextcloud/deck/pull/2750) Fix deck activity emails not being translated @nickvergessen
* [#2751](https://github.com/nextcloud/deck/pull/2751) Properly set author for activity events that are triggered by cron
* [#2796](https://github.com/nextcloud/deck/pull/2796) Install all needed php extensions
* [#2817](https://github.com/nextcloud/deck/pull/2817) Fix issues when creating a card from a talk message
## 1.3.0-beta2

View File

@@ -17,7 +17,7 @@
- 🚀 Get your project organized
</description>
<version>1.3.0-beta2</version>
<version>1.3.0</version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<namespace>Deck</namespace>

View File

@@ -197,6 +197,10 @@ class Application20 extends App implements IBootstrap {
$resourceManager->registerResourceProvider(ResourceProviderCard::class);
$symfonyAdapter->addListener('\OCP\Collaboration\Resources::loadAdditionalScripts', static function () {
if (strpos(\OC::$server->getRequest()->getPathInfo(), '/call/') === 0) {
// Talk integration has its own entrypoint which already includes collections handling
return;
}
Util::addScript('deck', 'collections');
});
}

View File

@@ -78,8 +78,8 @@ class CardController extends Controller {
* @param int $order
* @return \OCP\AppFramework\Db\Entity
*/
public function create($title, $stackId, $type = 'plain', $order = 999) {
return $this->cardService->create($title, $stackId, $type, $order, $this->userId);
public function create($title, $stackId, $type = 'plain', $order = 999, string $description = '') {
return $this->cardService->create($title, $stackId, $type, $order, $this->userId, $description);
}
/**

View File

@@ -52,5 +52,9 @@ class BeforeTemplateRenderedListener implements IEventListener {
if (strpos($this->request->getPathInfo(), '/apps/calendar') === 0) {
Util::addScript('deck', 'calendar');
}
if (strpos($this->request->getPathInfo(), '/call/') === 0) {
Util::addScript('deck', 'talk');
}
}
}

252
src/CardCreateDialog.vue Normal file
View File

@@ -0,0 +1,252 @@
<!--
- @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
-
- @author Julius Härtl <jus@bitgrid.net>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<Modal class="card-selector" @close="close">
<div class="modal-scroller">
<div v-if="!creating && !created" id="modal-inner" :class="{ 'icon-loading': loading }">
<h3>{{ t('deck', 'Create a new card') }}</h3>
<Multiselect v-model="selectedBoard"
:placeholder="t('deck', 'Select a board')"
:options="boards"
:disabled="loading"
label="title"
class="multiselect-board"
@select="fetchCardsFromBoard">
<template slot="singleLabel" slot-scope="props">
<span>
<span :style="{ 'backgroundColor': '#' + props.option.color }" class="board-bullet" />
<span>{{ props.option.title }}</span>
</span>
</template>
<template slot="option" slot-scope="props">
<span>
<span :style="{ 'backgroundColor': '#' + props.option.color }" class="board-bullet" />
<span>{{ props.option.title }}</span>
</span>
</template>
</Multiselect>
<Multiselect v-model="selectedStack"
:placeholder="t('deck', 'Select a list')"
:options="stacksFromBoard"
:max-height="100"
:disabled="loading || !selectedBoard"
class="multiselect-list"
label="title" />
<input v-model="pendingTitle"
type="text"
:placeholder="t('deck', 'Card title')"
:disabled="loading || !selectedStack">
<textarea v-model="pendingDescription" :disabled="loading || !selectedStack" />
<div class="modal-buttons">
<button @click="close">
{{ t('deck', 'Cancel') }}
</button>
<button :disabled="loading || !isBoardAndStackChoosen"
class="primary"
@click="select">
{{ action }}
</button>
</div>
</div>
<div v-else id="modal-inner">
<EmptyContent v-if="creating" icon="icon-loading">
{{ t('deck', 'Creating the new card') }}
</EmptyContent>
<EmptyContent v-else-if="created" icon="icon-checkmark">
{{ t('deck', '"{card}" was added to "{board}"', { card: pendingTitle, board: selectedBoard.title }) }}
<template #desc>
<button class="primary" @click="openNewCard">
{{ t('deck', 'Open card') }}
</button>
<button @click="close">
{{ t('deck', 'Close') }}
</button>
</template>
</EmptyContent>
</div>
</div>
</Modal>
</template>
<script>
import { generateUrl } from '@nextcloud/router'
import Modal from '@nextcloud/vue/dist/Components/Modal'
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
import axios from '@nextcloud/axios'
import { CardApi } from './services/CardApi'
const cardApi = new CardApi()
export default {
name: 'CardCreateDialog',
components: {
EmptyContent,
Modal,
Multiselect,
},
props: {
title: {
type: String,
default: '',
},
description: {
type: String,
default: '',
},
action: {
type: String,
default: t('deck', 'Create card'),
},
},
data() {
return {
boards: [],
stacksFromBoard: [],
loading: true,
pendingTitle: '',
pendingDescription: '',
selectedStack: '',
selectedBoard: '',
creating: false,
created: false,
newCard: null,
}
},
computed: {
isBoardAndStackChoosen() {
return !(this.selectedBoard === '')
},
},
beforeMount() {
this.fetchBoards()
},
mounted() {
this.pendingTitle = this.title
this.pendingDescription = this.description
},
methods: {
fetchBoards() {
axios.get(generateUrl('/apps/deck/boards')).then((response) => {
this.boards = response.data
this.loading = false
})
},
async fetchCardsFromBoard(board) {
try {
this.cardsFromBoard = []
const url = generateUrl('/apps/deck/stacks/' + board.id)
const response = await axios.get(url)
response.data.forEach(stack => {
this.stacksFromBoard.push(stack)
})
} catch (err) {
return err
}
},
close() {
this.$root.$emit('close')
},
async select() {
this.creating = true
const response = await cardApi.addCard({
boardId: this.selectedBoard.id,
stackId: this.selectedStack.id,
title: this.pendingTitle,
description: this.pendingDescription,
})
this.newCard = response
this.creating = false
this.created = true
// We do not emit here since we want to give feedback to the user that the card was created
// this.$root.$emit('select', createdCard)
},
openNewCard() {
window.location = generateUrl('/apps/deck') + `#/board/${this.selectedBoard.id}/card/${this.newCard.id}`
},
},
}
</script>
<style lang="scss" scoped>
.modal-scroller {
overflow: scroll;
max-height: calc(80vh - 40px);
margin: 10px;
}
#modal-inner {
width: 90vw;
max-width: 400px;
padding: 10px;
min-height: 200px;
}
.multiselect-board, .multiselect-list, input, textarea {
width: 100%;
margin-bottom: 10px !important;
}
ul {
min-height: 100px;
}
li {
padding: 6px;
border: 1px solid transparent;
}
li:hover, li:focus {
background-color: var(--color-background-dark);
}
.board-bullet {
display: inline-block;
width: 12px;
height: 12px;
border: none;
border-radius: 50%;
cursor: pointer;
}
.modal-buttons {
display: flex;
justify-content: flex-end;
}
.card-selector::v-deep .modal-container {
overflow: visible !important;
}
.empty-content {
margin-top: 5vh !important;
&::v-deep h2 {
margin-bottom: 5vh;
}
}
</style>

47
src/helpers/selector.js Normal file
View File

@@ -0,0 +1,47 @@
/*
* @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import Vue from 'vue'
const buildSelector = (selector, propsData = {}) => {
return new Promise((resolve, reject) => {
const container = document.createElement('div')
document.getElementById('body-user').append(container)
const View = Vue.extend(selector)
const ComponentVM = new View({
propsData,
}).$mount(container)
ComponentVM.$root.$on('close', () => {
ComponentVM.$el.remove()
ComponentVM.$destroy()
reject(new Error('Selection canceled'))
})
ComponentVM.$root.$on('select', (id) => {
ComponentVM.$el.remove()
ComponentVM.$destroy()
resolve(id)
})
})
}
export {
buildSelector,
}

View File

@@ -26,6 +26,8 @@ import BoardSelector from './BoardSelector'
import CardSelector from './CardSelector'
import './../css/collections.css'
import FileSharingPicker from './views/FileSharingPicker'
import { buildSelector } from './helpers/selector'
// eslint-disable-next-line
__webpack_nonce__ = btoa(OC.requestToken);
// eslint-disable-next-line
@@ -41,61 +43,16 @@ window.addEventListener('DOMContentLoaded', () => {
} else {
console.error('OCA.Sharing.ShareSearch not ready')
}
});
((function(OCP) {
OCP.Collaboration.registerType('deck', {
action: () => {
return new Promise((resolve, reject) => {
const container = document.createElement('div')
container.id = 'deck-board-select'
const body = document.getElementById('body-user')
body.append(container)
const ComponentVM = new Vue({
render: h => h(BoardSelector),
})
ComponentVM.$mount(container)
ComponentVM.$root.$on('close', () => {
ComponentVM.$el.remove()
ComponentVM.$destroy()
reject(new Error('Board selection canceled'))
})
ComponentVM.$root.$on('select', (id) => {
resolve(id)
ComponentVM.$el.remove()
ComponentVM.$destroy()
})
})
},
window.OCP.Collaboration.registerType('deck', {
action: () => buildSelector(BoardSelector),
typeString: t('deck', 'Link to a board'),
typeIconClass: 'icon-deck',
})
OCP.Collaboration.registerType('deck-card', {
action: () => {
return new Promise((resolve, reject) => {
const container = document.createElement('div')
container.id = 'deck-board-select'
const body = document.getElementById('body-user')
body.append(container)
const ComponentVM = new Vue({
render: h => h(CardSelector),
})
ComponentVM.$mount(container)
ComponentVM.$root.$on('close', () => {
ComponentVM.$el.remove()
ComponentVM.$destroy()
reject(new Error('Card selection canceled'))
})
ComponentVM.$root.$on('select', (id) => {
resolve(id)
ComponentVM.$el.remove()
ComponentVM.$destroy()
})
})
},
window.OCP.Collaboration.registerType('deck-card', {
action: () => buildSelector(CardSelector),
typeString: t('deck', 'Link to a card'),
typeIconClass: 'icon-deck',
})
})(window.OCP))
})

62
src/init-talk.js Normal file
View File

@@ -0,0 +1,62 @@
/*
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import Vue from 'vue'
import { generateUrl } from '@nextcloud/router'
import CardCreateDialog from './CardCreateDialog'
import { buildSelector } from './helpers/selector'
import './init-collections'
// eslint-disable-next-line
__webpack_nonce__ = btoa(OC.requestToken);
// eslint-disable-next-line
__webpack_public_path__ = OC.linkTo('deck', 'js/');
Vue.prototype.t = t
Vue.prototype.n = n
Vue.prototype.OC = OC
window.addEventListener('DOMContentLoaded', () => {
if (!window.OCA?.Talk?.registerMessageAction) {
return
}
window.OCA.Talk.registerMessageAction({
label: t('deck', 'Create a card'),
icon: 'icon-deck',
async callback({ message: { message, actorDisplayName }, metadata: { name: conversationName, token: conversationToken } }) {
const shortenedMessageCandidate = message.replace(/^(.{255}[^\s]*).*/, '$1')
const shortenedMessage = shortenedMessageCandidate === '' ? message.substr(0, 255) : shortenedMessageCandidate
try {
await buildSelector(CardCreateDialog, {
title: shortenedMessage,
description: message + '\n\n' + '['
+ t('deck', 'Message from {author} in {conversationName}', { author: actorDisplayName, conversationName })
+ '](' + generateUrl('/call/' + conversationToken) + ')',
})
} catch (e) {
console.debug('Card creation dialog was canceled')
}
},
})
})

View File

@@ -7,6 +7,7 @@ const config = {
collections: path.join(__dirname, 'src', 'init-collections.js'),
dashboard: path.join(__dirname, 'src', 'init-dashboard.js'),
calendar: path.join(__dirname, 'src', 'init-calendar.js'),
talk: path.join(__dirname, 'src', 'init-talk.js'),
},
output: {
filename: '[name].js',