@@ -33,6 +33,7 @@ import { translate as t } from '@nextcloud/l10n'
3333import moment from '@nextcloud/moment'
3434
3535import ICAL from 'ical.js'
36+ import { randomUUID } from '../utils/crypto.js'
3637
3738const state = {
3839 tasks : { } ,
@@ -859,6 +860,82 @@ const actions = {
859860 }
860861 } ,
861862
863+ /**
864+ * Duplicates an existing task. Subtasks are duplicated recursively.
865+ *
866+ * @param {object } context The store context
867+ * @param {object } payload Destructuring object
868+ * @param {Task } payload.task The task to duplicate
869+ * @param {Calendar } [payload.calendar] The calendar to create the duplicate in (defaults to original task calendar)
870+ * @param {Task|null } [payload.parent] The parent task to attach the duplicate to (optional)
871+ * @return {Promise<Task> } The newly created duplicate task
872+ */
873+ async duplicateTask ( context , payload ) {
874+ // Support being called with either the Task directly or a payload object
875+ // called as: duplicateTask(task) or duplicateTask({ task, calendar, parent })
876+ const task = payload && payload . task ? payload . task : payload
877+ const calendar = payload && payload . calendar ? payload . calendar : ( task ? task . calendar : null )
878+ const parent = payload && Object . prototype . hasOwnProperty . call ( payload , 'parent' ) ? payload . parent : null
879+
880+ // Don't try to duplicate non-existing tasks
881+ if ( ! task ) {
882+ return null
883+ }
884+ // Don't try to duplicate tasks into read-only calendars
885+ if ( ! calendar || calendar . readOnly ) {
886+ return null
887+ }
888+ // Don't duplicate tasks with access class not PUBLIC into calendars shared with me
889+ if ( calendar . isSharedWithMe && task . class !== 'PUBLIC' ) {
890+ return null
891+ }
892+
893+ // Create a new Task from the existing task's jCal
894+ const vData = ICAL . stringify ( task . jCal )
895+ const newTask = new Task ( vData , calendar )
896+
897+ // Assign a new UID and created timestamp
898+ newTask . uid = randomUUID ( )
899+ newTask . created = ICAL . Time . fromJSDate ( new Date ( ) , true )
900+ newTask . dav = null
901+ newTask . conflict = false
902+
903+ // If a parent was provided, link to it. Otherwise, if the original task had
904+ // a related parent and that parent exists in the target calendar, keep relation.
905+ if ( parent ) {
906+ newTask . related = parent . uid
907+ } else if ( task . related ) {
908+ const existingParent = context . getters . getTaskByUid ( task . related )
909+ if ( existingParent && existingParent . calendar && existingParent . calendar . id === calendar . id ) {
910+ newTask . related = task . related
911+ } else {
912+ newTask . related = null
913+ }
914+ }
915+
916+ // Create the new vObject on the server
917+ try {
918+ const response = await calendar . dav . createVObject ( ICAL . stringify ( newTask . jCal ) )
919+ newTask . dav = response
920+ newTask . syncStatus = new SyncStatus ( 'success' , t ( 'tasks' , 'Successfully duplicated the task.' ) )
921+ context . commit ( 'appendTask' , newTask )
922+ context . commit ( 'addTaskToCalendar' , newTask )
923+ const parentLocal = context . getters . getTaskByUid ( newTask . related )
924+ context . commit ( 'addTaskToParent' , { task : newTask , parent : parentLocal } )
925+ } catch ( error ) {
926+ console . error ( error )
927+ showError ( t ( 'tasks' , 'Could not duplicate the task.' ) )
928+ return null
929+ }
930+
931+ // Duplicate subtasks recursively, attaching them to the new parent
932+ await Promise . all ( Object . values ( task . subTasks ) . map ( async ( subTask ) => {
933+ await context . dispatch ( 'duplicateTask' , { task : subTask , calendar, parent : newTask } )
934+ } ) )
935+
936+ return newTask
937+ } ,
938+
862939 /**
863940 * Deletes a task
864941 *
0 commit comments