feat: paste text and automatically create a new task

This commit is contained in:
Julien Calixte
2026-01-24 13:15:40 +01:00
parent 667ca5ab95
commit b1f5aaaec2
6 changed files with 84 additions and 9 deletions

View File

@@ -1,10 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { createUuid } from '@/shared/create-uuid' import { createUuid } from '@/shared/create-uuid'
import type { Stepable } from '../interfaces/stepable'
import TaskForm from './TaskForm.vue' import TaskForm from './TaskForm.vue'
const props = defineProps<{
initialSteps?: Stepable[]
}>()
const id = createUuid() const id = createUuid()
</script> </script>
<template> <template>
<task-form :id="id" :action="'new'" /> <task-form :id="id" :initial-steps="props.initialSteps" />
</template> </template>

View File

@@ -14,16 +14,19 @@ const router = useRouter()
const props = defineProps<{ const props = defineProps<{
id: string id: string
initialTask?: Taskable initialTask?: Taskable
initialSteps?: Stepable[]
}>() }>()
const id = computed(() => props.id) const id = computed(() => props.id)
const hasTasks = computed(() => store.tasks.length > 0) const hasTasks = computed(() => store.tasks.length > 0)
const steps = ref<Stepable[]>( const steps = ref<Stepable[]>(
props.initialTask props.initialSteps?.length
? Task.fromTaskable(props.initialTask).steps ? props.initialSteps
: hasTasks.value : props.initialTask
? [] ? Task.fromTaskable(props.initialTask).steps
: exampleSteps : hasTasks.value
? []
: exampleSteps
) )
const title = ref(props.initialTask?.title ?? '') const title = ref(props.initialTask?.title ?? '')

View File

@@ -93,4 +93,40 @@ describe('adapt steps to textarea value', () => {
`"9b237c28d5254f2b819fa66c853a9a60-2"` `"9b237c28d5254f2b819fa66c853a9a60-2"`
) )
}) })
it('parses steps with unchecked checkbox format', () => {
const stepInTextarea = '- [ ] step with checkbox | 20'
const expectedStep = fixtureStep({
id: expect.any(String),
title: 'step with checkbox',
estimation: 20
})
expect(adaptTextareaToSteps(stepInTextarea)).toEqual([expectedStep])
})
it('parses steps with checked checkbox format', () => {
const stepInTextarea = '- [x] completed step | 15'
const expectedStep = fixtureStep({
id: expect.any(String),
title: 'completed step',
estimation: 15
})
expect(adaptTextareaToSteps(stepInTextarea)).toEqual([expectedStep])
})
it('parses checkbox steps without estimation', () => {
const stepInTextarea = '- [ ] step without estimation'
const expectedStep = fixtureStep({
id: expect.any(String),
title: 'step without estimation',
estimation: 0
})
expect(adaptTextareaToSteps(stepInTextarea)).toEqual([expectedStep])
})
}) })

View File

@@ -9,7 +9,7 @@ const extractTitleAndEstimationFromStep = (
): [string, number] => { ): [string, number] => {
const [rawTitle, rawEstimation] = rawStep const [rawTitle, rawEstimation] = rawStep
.trim() .trim()
.replace(/^-\s*/, '') .replace(/^-\s*(\[[ x]\]\s*)?/, '')
.split('|') .split('|')
const title = rawTitle.trim() const title = rawTitle.trim()

View File

@@ -1,9 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import TaskList from '@/modules/task/components/TaskList.vue' import TaskList from '@/modules/task/components/TaskList.vue'
import { adaptTextareaToSteps } from '@/modules/task/infra/adaptStepsToTextarea'
import { useTaskStore } from '@/modules/task/stores/useTask.store' import { useTaskStore } from '@/modules/task/stores/useTask.store'
import { computed } from 'vue' import { computed, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
const taskStore = useTaskStore() const taskStore = useTaskStore()
const router = useRouter()
const hasTask = computed(() => taskStore.tasks.length > 0) const hasTask = computed(() => taskStore.tasks.length > 0)
@@ -12,6 +15,28 @@ const resetTasks = () => {
taskStore.reset() taskStore.reset()
} }
} }
const handlePaste = (event: ClipboardEvent) => {
const clipboardText = event.clipboardData?.getData('text')
if (!clipboardText) {
return
}
const steps = adaptTextareaToSteps(clipboardText)
if (steps.length === 0) {
return
}
router.push({ name: 'new-task', state: { initialSteps: JSON.stringify(steps) } })
}
onMounted(() => {
document.addEventListener('paste', handlePaste)
})
onUnmounted(() => {
document.removeEventListener('paste', handlePaste)
})
</script> </script>
<template> <template>

View File

@@ -1,9 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import NewTaskForm from '@/modules/task/components/NewTaskForm.vue' import NewTaskForm from '@/modules/task/components/NewTaskForm.vue'
import type { Stepable } from '@/modules/task/interfaces/stepable'
const rawInitialSteps = history.state?.initialSteps as string | undefined
const initialSteps = rawInitialSteps
? (JSON.parse(rawInitialSteps) as Stepable[])
: undefined
</script> </script>
<template> <template>
<div class="new-task"> <div class="new-task">
<NewTaskForm /> <NewTaskForm :initial-steps="initialSteps" />
</div> </div>
</template> </template>