Create task from talk message
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
This commit is contained in:
parent
4873158062
commit
033d851712
|
@ -0,0 +1 @@
|
||||||
|
@include icon-black-white('tasks', 'tasks', 1);
|
|
@ -23,10 +23,12 @@
|
||||||
namespace OCA\Tasks\AppInfo;
|
namespace OCA\Tasks\AppInfo;
|
||||||
|
|
||||||
use OCA\Tasks\Dashboard\TasksWidget;
|
use OCA\Tasks\Dashboard\TasksWidget;
|
||||||
|
use OCA\Tasks\Listeners\BeforeTemplateRenderedListener;
|
||||||
use OCP\AppFramework\App;
|
use OCP\AppFramework\App;
|
||||||
use OCP\AppFramework\Bootstrap\IBootContext;
|
use OCP\AppFramework\Bootstrap\IBootContext;
|
||||||
use OCP\AppFramework\Bootstrap\IBootstrap;
|
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||||
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||||
|
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
|
||||||
|
|
||||||
class Application extends App implements IBootstrap {
|
class Application extends App implements IBootstrap {
|
||||||
|
|
||||||
|
@ -42,6 +44,8 @@ class Application extends App implements IBootstrap {
|
||||||
|
|
||||||
public function register(IRegistrationContext $context): void {
|
public function register(IRegistrationContext $context): void {
|
||||||
$context->registerDashboardWidget(TasksWidget::class);
|
$context->registerDashboardWidget(TasksWidget::class);
|
||||||
|
|
||||||
|
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function boot(IBootContext $context): void {
|
public function boot(IBootContext $context): void {
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2021 Jakob Röhrl <jakob.roehrl@web.de>
|
||||||
|
*
|
||||||
|
* @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
* @author Jakob Röhrl <jakob.roehrl@web.de>
|
||||||
|
*
|
||||||
|
* @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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
|
namespace OCA\Tasks\Listeners;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
|
||||||
|
use OCP\EventDispatcher\Event;
|
||||||
|
use OCP\EventDispatcher\IEventListener;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\Util;
|
||||||
|
|
||||||
|
class BeforeTemplateRenderedListener implements IEventListener {
|
||||||
|
private $request;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request) {
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(Event $event): void {
|
||||||
|
if (!($event instanceof BeforeTemplateRenderedEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$event->isLoggedIn()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pathInfo = $this->request->getPathInfo();
|
||||||
|
if (strpos($pathInfo, '/call/') === 0 || strpos($pathInfo, '/apps/spreed') === 0) {
|
||||||
|
Util::addScript('tasks', 'tasks-talk');
|
||||||
|
Util::addStyle('tasks', 'tasks-talk');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
:disabled="isDisabled"
|
:disabled="isDisabled"
|
||||||
:options="calendars"
|
:options="calendars"
|
||||||
:value="calendar"
|
:value="calendar"
|
||||||
:placeholder="$t('tasks', 'Select a calendar')"
|
:placeholder="translate('tasks', 'Select a calendar')"
|
||||||
@select="change">
|
@select="change">
|
||||||
<template slot="singleLabel" slot-scope="scope">
|
<template slot="singleLabel" slot-scope="scope">
|
||||||
<CalendarPickerOption v-bind="scope.option" />
|
<CalendarPickerOption v-bind="scope.option" />
|
||||||
|
@ -43,7 +43,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
color=""
|
color=""
|
||||||
owner=""
|
owner=""
|
||||||
:is-shared-with-me="false"
|
:is-shared-with-me="false"
|
||||||
:display-name="$t('tasks', 'No calendar matches the search.')" />
|
:display-name="translate('tasks', 'No calendar matches the search.')" />
|
||||||
</template>
|
</template>
|
||||||
</Multiselect>
|
</Multiselect>
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,6 +52,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
<script>
|
<script>
|
||||||
import CalendarPickerOption from './CalendarPickerOption.vue'
|
import CalendarPickerOption from './CalendarPickerOption.vue'
|
||||||
|
|
||||||
|
import { translate } from '@nextcloud/l10n'
|
||||||
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
|
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -62,7 +63,7 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
calendar: {
|
calendar: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
default: null,
|
||||||
},
|
},
|
||||||
calendars: {
|
calendars: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
@ -79,6 +80,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
translate,
|
||||||
/**
|
/**
|
||||||
* TODO: this should emit the calendar id instead
|
* TODO: this should emit the calendar id instead
|
||||||
* @param {Object} newCalendar The selected calendar
|
* @param {Object} newCalendar The selected calendar
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
<!--
|
||||||
|
Nextcloud - Tasks
|
||||||
|
|
||||||
|
@author Julius Härtl
|
||||||
|
@copyright 2021 Julius Härtl <jus@bitgrid.net>
|
||||||
|
|
||||||
|
@author Jakob Röhrl
|
||||||
|
@copyright 2021 Jakob Röhrl <jakob.roehrl@web.de>
|
||||||
|
|
||||||
|
@author Raimund Schlüßler
|
||||||
|
@copyright 2021 Raimund Schlüßler <raimund.schluessler@mailbox.org>
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 3 of the License, or any later version.
|
||||||
|
|
||||||
|
This library 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 library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal class="task-selector" @close="close">
|
||||||
|
<div class="modal-scroller">
|
||||||
|
<div v-if="!creating && !created" id="modal-inner" :class="{ 'icon-loading': loading }">
|
||||||
|
<h3>{{ t('tasks', 'Create a new task') }}</h3>
|
||||||
|
|
||||||
|
<CalendarPickerItem
|
||||||
|
:disabled="loading"
|
||||||
|
:calendar="pendingCalendar"
|
||||||
|
:calendars="writableCalendars"
|
||||||
|
@changeCalendar="changeCalendar" />
|
||||||
|
|
||||||
|
<input v-model="pendingTitle"
|
||||||
|
type="text"
|
||||||
|
:placeholder="t('tasks', 'Create a new task')"
|
||||||
|
:disabled="loading">
|
||||||
|
|
||||||
|
<textarea v-model="pendingDescription"
|
||||||
|
:disabled="loading" />
|
||||||
|
<div class="modal-buttons">
|
||||||
|
<button @click="close">
|
||||||
|
{{ t('tasks', 'Cancel') }}
|
||||||
|
</button>
|
||||||
|
<button :disabled="loading"
|
||||||
|
class="primary"
|
||||||
|
@click="addTask">
|
||||||
|
{{ t('tasks', 'Create task') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else id="modal-inner">
|
||||||
|
<EmptyContent v-if="creating" icon="icon-loading">
|
||||||
|
{{ t('tasks', 'Creating the new task…') }}
|
||||||
|
</EmptyContent>
|
||||||
|
<EmptyContent v-else-if="created" icon="icon-checkmark">
|
||||||
|
{{ t('tasks', '"{task}" was added to "{calendar}"', { task: pendingTitle, calendar: pendingCalendar.displayName }, undefined, { sanitize: false, escape: false }) }}
|
||||||
|
<template #desc>
|
||||||
|
<button class="primary" @click="openNewTask">
|
||||||
|
{{ t('tasks', 'Open task') }}
|
||||||
|
</button>
|
||||||
|
<button @click="close">
|
||||||
|
{{ t('tasks', 'Close') }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</EmptyContent>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import CalendarPickerItem from './AppSidebar/CalendarPickerItem.vue'
|
||||||
|
import client from '../services/cdav.js'
|
||||||
|
|
||||||
|
import { translate as t } from '@nextcloud/l10n'
|
||||||
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
|
||||||
|
import Modal from '@nextcloud/vue/dist/Components/Modal'
|
||||||
|
|
||||||
|
import { mapGetters, mapActions } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TaskCreateDialog',
|
||||||
|
components: {
|
||||||
|
CalendarPickerItem,
|
||||||
|
EmptyContent,
|
||||||
|
Modal,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
pendingTitle: '',
|
||||||
|
pendingDescription: '',
|
||||||
|
pendingCalendar: null,
|
||||||
|
loading: true,
|
||||||
|
creating: false,
|
||||||
|
created: false,
|
||||||
|
newTask: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
writableCalendars: 'getSortedWritableCalendars',
|
||||||
|
defaultCalendar: 'getDefaultCalendar',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeMount() {
|
||||||
|
this.fetchCalendars()
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.pendingTitle = this.title
|
||||||
|
this.pendingDescription = this.description
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
...mapActions([
|
||||||
|
'createTask',
|
||||||
|
]),
|
||||||
|
|
||||||
|
t,
|
||||||
|
|
||||||
|
changeCalendar(calendar) {
|
||||||
|
this.pendingCalendar = calendar
|
||||||
|
},
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.$root.$emit('close')
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchCalendars() {
|
||||||
|
this.loading = true
|
||||||
|
await client.connect({ enableCalDAV: true })
|
||||||
|
await this.$store.dispatch('fetchCurrentUserPrincipal')
|
||||||
|
await this.$store.dispatch('getCalendars')
|
||||||
|
// TODO: Would be good to select the default calendar instead of the first one
|
||||||
|
this.pendingCalendar = this.writableCalendars[0]
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
|
||||||
|
async addTask() {
|
||||||
|
this.creating = true
|
||||||
|
const task = {
|
||||||
|
summary: this.pendingTitle,
|
||||||
|
note: this.pendingDescription,
|
||||||
|
calendar: this.pendingCalendar,
|
||||||
|
}
|
||||||
|
this.newTask = await this.createTask(task)
|
||||||
|
this.creating = false
|
||||||
|
this.created = true
|
||||||
|
},
|
||||||
|
openNewTask() {
|
||||||
|
window.location = generateUrl('apps/tasks') + `/#/calendars/${this.pendingCalendar.id}/tasks/${this.newTask.uri}`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, textarea {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-selector::v-deep .modal-container {
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* @copyright Copyright (c) 2021 Jakob Röhrl <jakob.roehrl@web.de>
|
||||||
|
*
|
||||||
|
* @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
* @author Jakob Röhrl <jakob.roehrl@web.de>
|
||||||
|
*
|
||||||
|
* @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 store from '../store/store.js'
|
||||||
|
|
||||||
|
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,
|
||||||
|
store,
|
||||||
|
}).$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,
|
||||||
|
}
|
|
@ -320,7 +320,14 @@ const mutations = {
|
||||||
*/
|
*/
|
||||||
addCalendar(state, calendar) {
|
addCalendar(state, calendar) {
|
||||||
// extend the calendar to the default model
|
// extend the calendar to the default model
|
||||||
state.calendars.push(Object.assign({}, calendarModel, calendar))
|
calendar = Object.assign({}, calendarModel, calendar)
|
||||||
|
// Only add the calendar if it is not already present
|
||||||
|
if (state.calendars.some(cal => {
|
||||||
|
return cal.id === calendar.id
|
||||||
|
})) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.calendars.push(calendar)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { findVTODObyUid } from './cdav-requests.js'
|
||||||
|
|
||||||
import { showError } from '@nextcloud/dialogs'
|
import { showError } from '@nextcloud/dialogs'
|
||||||
import { emit } from '@nextcloud/event-bus'
|
import { emit } from '@nextcloud/event-bus'
|
||||||
|
import { translate as t } from '@nextcloud/l10n'
|
||||||
import moment from '@nextcloud/moment'
|
import moment from '@nextcloud/moment'
|
||||||
|
|
||||||
import ICAL from 'ical.js'
|
import ICAL from 'ical.js'
|
||||||
|
@ -702,36 +703,34 @@ const actions = {
|
||||||
const vData = ICAL.stringify(task.jCal)
|
const vData = ICAL.stringify(task.jCal)
|
||||||
|
|
||||||
if (!task.dav) {
|
if (!task.dav) {
|
||||||
await task.calendar.dav.createVObject(vData)
|
const response = await task.calendar.dav.createVObject(vData)
|
||||||
.then((response) => {
|
Vue.set(task, 'dav', response)
|
||||||
Vue.set(task, 'dav', response)
|
task.syncStatus = new SyncStatus('success', t('tasks', 'Successfully created the task.'))
|
||||||
task.syncStatus = new SyncStatus('success', OCA.Tasks.$t('tasks', 'Successfully created the task.'))
|
context.commit('appendTask', task)
|
||||||
context.commit('appendTask', task)
|
context.commit('addTaskToCalendar', task)
|
||||||
context.commit('addTaskToCalendar', task)
|
const parent = context.getters.getTaskByUid(task.related)
|
||||||
const parent = context.getters.getTaskByUid(task.related)
|
context.commit('addTaskToParent', { task, parent })
|
||||||
context.commit('addTaskToParent', { task, parent })
|
|
||||||
|
|
||||||
// Open the details view for the new task
|
// In case the task is created in Talk, we don't have a route
|
||||||
const calendarId = context.rootState.route.params.calendarId
|
// Only open the details view if there is enough space or if it is already open.
|
||||||
const collectionId = context.rootState.route.params.collectionId
|
if (context.rootState.route !== undefined && (document.documentElement.clientWidth >= 768 || context.rootState.route?.params.taskId !== undefined)) {
|
||||||
// Only open the details view if there is enough space or if it is already open.
|
// Open the details view for the new task
|
||||||
if (document.documentElement.clientWidth >= 768 || context.rootState.route.params.taskId !== undefined) {
|
const calendarId = context.rootState.route.params.calendarId
|
||||||
if (calendarId) {
|
const collectionId = context.rootState.route.params.collectionId
|
||||||
router.push({ name: 'calendarsTask', params: { calendarId, taskId: task.uri } })
|
if (calendarId) {
|
||||||
} else if (collectionId) {
|
router.push({ name: 'calendarsTask', params: { calendarId, taskId: task.uri } })
|
||||||
if (collectionId === 'week') {
|
} else if (collectionId) {
|
||||||
router.push({
|
if (collectionId === 'week') {
|
||||||
name: 'collectionsParamTask',
|
router.push({
|
||||||
params: { collectionId, taskId: task.uri, collectionParam: '0' },
|
name: 'collectionsParamTask',
|
||||||
})
|
params: { collectionId, taskId: task.uri, collectionParam: '0' },
|
||||||
} else {
|
})
|
||||||
router.push({ name: 'collectionsTask', params: { collectionId, taskId: task.uri } })
|
} else {
|
||||||
}
|
router.push({ name: 'collectionsTask', params: { collectionId, taskId: task.uri } })
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
}
|
||||||
.catch((error) => { throw error })
|
return task
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -783,7 +782,7 @@ const actions = {
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.debug(error)
|
console.debug(error)
|
||||||
task.syncStatus = new SyncStatus('error', OCA.Tasks.$t('tasks', 'Could not delete the task.'))
|
task.syncStatus = new SyncStatus('error', t('tasks', 'Could not delete the task.'))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
deleteTaskFromStore()
|
deleteTaskFromStore()
|
||||||
|
@ -875,10 +874,10 @@ const actions = {
|
||||||
|
|
||||||
if (!task.conflict) {
|
if (!task.conflict) {
|
||||||
task.dav.data = vCalendar
|
task.dav.data = vCalendar
|
||||||
task.syncStatus = new SyncStatus('sync', OCA.Tasks.$t('tasks', 'Synchronizing to the server.'))
|
task.syncStatus = new SyncStatus('sync', t('tasks', 'Synchronizing to the server.'))
|
||||||
return task.dav.update()
|
return task.dav.update()
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
task.syncStatus = new SyncStatus('success', OCA.Tasks.$t('tasks', 'Task successfully saved to server.'))
|
task.syncStatus = new SyncStatus('success', t('tasks', 'Task successfully saved to server.'))
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
// Wrong etag, we most likely have a conflict
|
// Wrong etag, we most likely have a conflict
|
||||||
|
@ -886,13 +885,13 @@ const actions = {
|
||||||
// Saving the new etag so that the user can manually
|
// Saving the new etag so that the user can manually
|
||||||
// trigger a fetchCompleteData without any further errors
|
// trigger a fetchCompleteData without any further errors
|
||||||
task.conflict = error.xhr.getResponseHeader('etag')
|
task.conflict = error.xhr.getResponseHeader('etag')
|
||||||
task.syncStatus = new SyncStatus('conflict', OCA.Tasks.$t('tasks', 'Could not update the task because it was changed on the server. Please click to refresh it, local changes will be discarded.'))
|
task.syncStatus = new SyncStatus('conflict', t('tasks', 'Could not update the task because it was changed on the server. Please click to refresh it, local changes will be discarded.'))
|
||||||
} else {
|
} else {
|
||||||
task.syncStatus = new SyncStatus('error', OCA.Tasks.$t('tasks', 'Could not update the task.'))
|
task.syncStatus = new SyncStatus('error', t('tasks', 'Could not update the task.'))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
task.syncStatus = new SyncStatus('conflict', OCA.Tasks.$t('tasks', 'Could not update the task because it was changed on the server. Please click to refresh it, local changes will be discarded.'))
|
task.syncStatus = new SyncStatus('conflict', t('tasks', 'Could not update the task because it was changed on the server. Please click to refresh it, local changes will be discarded.'))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1304,7 +1303,7 @@ const actions = {
|
||||||
return task.dav.fetchCompleteData()
|
return task.dav.fetchCompleteData()
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const newTask = new Task(task.dav.data, task.calendar)
|
const newTask = new Task(task.dav.data, task.calendar)
|
||||||
task.syncStatus = new SyncStatus('success', OCA.Tasks.$t('tasks', 'Successfully updated the task.'))
|
task.syncStatus = new SyncStatus('success', t('tasks', 'Successfully updated the task.'))
|
||||||
task.conflict = false
|
task.conflict = false
|
||||||
context.commit('updateTask', newTask)
|
context.commit('updateTask', newTask)
|
||||||
})
|
})
|
||||||
|
@ -1376,11 +1375,11 @@ const actions = {
|
||||||
// Remove the task from the calendar, add it to the new one
|
// Remove the task from the calendar, add it to the new one
|
||||||
context.commit('addTaskToCalendar', task)
|
context.commit('addTaskToCalendar', task)
|
||||||
context.commit('appendTask', task)
|
context.commit('appendTask', task)
|
||||||
task.syncStatus = new SyncStatus('success', OCA.Tasks.$t('tasks', 'Task successfully moved to new calendar.'))
|
task.syncStatus = new SyncStatus('success', t('tasks', 'Task successfully moved to new calendar.'))
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
showError(OCA.Tasks.$t('tasks', 'An error occurred'))
|
showError(t('tasks', 'An error occurred'))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
/**
|
||||||
|
* Nextcloud - Tasks
|
||||||
|
*
|
||||||
|
* @author Julius Härtl
|
||||||
|
* @copyright 2021 Julius Härtl <jus@bitgrid.net>
|
||||||
|
*
|
||||||
|
* @author Jakob Röhrl
|
||||||
|
* @copyright 2021 Jakob Röhrl <jakob.roehrl@web.de>
|
||||||
|
*
|
||||||
|
* @author Raimund Schlüßler
|
||||||
|
* @copyright 2021 Raimund Schlüßler <raimund.schluessler@mailbox.org>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 3 of the License, or any later version.
|
||||||
|
*
|
||||||
|
* This library 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 library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TaskCreateDialog from './components/TaskCreateDialog'
|
||||||
|
import { buildSelector } from './helpers/selector'
|
||||||
|
|
||||||
|
import { getRequestToken } from '@nextcloud/auth'
|
||||||
|
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
|
||||||
|
import { generateUrl, generateFilePath } from '@nextcloud/router'
|
||||||
|
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
__webpack_nonce__ = btoa(getRequestToken())
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
__webpack_public_path__ = generateFilePath('tasks', '', '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('tasks', 'Create a task'),
|
||||||
|
icon: 'icon-tasks',
|
||||||
|
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(TaskCreateDialog, {
|
||||||
|
title: shortenedMessage,
|
||||||
|
description: message + '\n\n' + '['
|
||||||
|
+ t('tasks', 'Message from {author} in {conversationName}', { author: actorDisplayName, conversationName })
|
||||||
|
+ '](' + generateUrl('/call/' + conversationToken) + ')',
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.debug('Task creation dialog was canceled')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
|
@ -24,4 +24,14 @@ export class OC {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
L10N = {
|
||||||
|
translate(app, text) {
|
||||||
|
return text
|
||||||
|
},
|
||||||
|
|
||||||
|
translatePlural(app, text) {
|
||||||
|
return text
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ const path = require('path')
|
||||||
webpackConfig.entry = {
|
webpackConfig.entry = {
|
||||||
...webpackConfig.entry,
|
...webpackConfig.entry,
|
||||||
dashboard: path.join(__dirname, 'src', 'dashboard.js'),
|
dashboard: path.join(__dirname, 'src', 'dashboard.js'),
|
||||||
|
talk: path.join(__dirname, 'src', 'talk.js'),
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = webpackConfig
|
module.exports = webpackConfig
|
||||||
|
|
Loading…
Reference in New Issue