From 28ee25fd120054d0afcfe98f6d7be68fd42eeb4a Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Wed, 24 Jun 2026 08:43:38 +0100 Subject: [PATCH] debugging ticket creation, lost spell checker --- .../views/components/html-editor.blade.php | 34 +++++++++++++++---- .../TicketCreateEditorSyncFilesTest.php | 30 +++++++++++----- Modules/Helpdesk/changelog.yaml | 12 +++++++ 3 files changed, 61 insertions(+), 15 deletions(-) diff --git a/Modules/Helpdesk/Resources/views/components/html-editor.blade.php b/Modules/Helpdesk/Resources/views/components/html-editor.blade.php index 1491907c..b52ebed7 100644 --- a/Modules/Helpdesk/Resources/views/components/html-editor.blade.php +++ b/Modules/Helpdesk/Resources/views/components/html-editor.blade.php @@ -14,29 +14,49 @@
whereDoesntStartWith('wire:model')->merge(['class' => 'space-y-2']) }} @if ($model) data-helpdesk-editor-model="{{ $model }}" @endif x-on:helpdesk-editor-reset.window="if ($event.detail.model === model) clear()" - x-on:helpdesk-editor-sync.window="if ($event.detail.model === model) sync()" x-data="{ + x-on:helpdesk-editor-sync.window="if ($event.detail.model === model) sync(false)" x-data="{ model: @js($model), value: @js($initialValue), - sync() { + submitHandler: null, + sync(live = false) { this.value = this.$refs.editor.innerHTML; + if (this.model) { - this.$wire.set(this.model, this.value); + this.$wire.set(this.model, this.value, live); } }, command(action, value = null) { this.$refs.editor.focus(); document.execCommand(action, false, value); - this.sync(); + this.sync(false); }, clear() { this.value = ''; this.$refs.editor.innerHTML = ''; + if (this.model) { - this.$wire.set(this.model, ''); + this.$wire.set(this.model, '', false); } }, init() { this.$refs.editor.innerHTML = this.value || ''; + + this.submitHandler = () => this.sync(false); + + this.$nextTick(() => { + const form = this.$root.closest('form'); + + if (form) { + form.addEventListener('submit', this.submitHandler, { capture: true }); + } + }); + }, + destroy() { + const form = this.$root.closest('form'); + + if (form && this.submitHandler) { + form.removeEventListener('submit', this.submitHandler, { capture: true }); + } } }"> @@ -66,9 +86,9 @@ class="rounded-lg px-2 py-1 text-sm hover:bg-zinc-200 dark:hover:bg-zinc-800">Clear
-
diff --git a/Modules/Helpdesk/Tests/Feature/TicketCreateEditorSyncFilesTest.php b/Modules/Helpdesk/Tests/Feature/TicketCreateEditorSyncFilesTest.php index 6dd16e76..09ccff9c 100644 --- a/Modules/Helpdesk/Tests/Feature/TicketCreateEditorSyncFilesTest.php +++ b/Modules/Helpdesk/Tests/Feature/TicketCreateEditorSyncFilesTest.php @@ -15,17 +15,31 @@ final class TicketCreateEditorSyncFilesTest extends TestCase public function test_ticket_create_editor_pushes_details_body_to_livewire_before_validation(): void { $view = file_get_contents(base_path('Modules/Helpdesk/Resources/views/livewire/ticket-create.blade.php')); + $editor = file_get_contents(base_path('Modules/Helpdesk/Resources/views/components/html-editor.blade.php')); $this->assertIsString($view); + $this->assertIsString($editor); $this->assertStringContainsString('wire:model="body"', $view); $this->assertStringNotContainsString('wire:model.live="body"', $view); - $this->assertStringContainsString('submit', $view); - $this->assertTrue( - str_contains($view, 'Livewire') - || str_contains($view, '$wire') - || str_contains($view, '@this') - || str_contains($view, 'wire:model="body"'), - 'Ticket create editor must keep the body field synchronised with Livewire before validation.' - ); + $this->assertStringContainsString('form.addEventListener(\'submit\'', $editor); + $this->assertStringContainsString('this.$wire.set(this.model, this.value, false)', $editor); + } + + /** + * The rich text editor must not fire a Livewire request for every keystroke, + * otherwise the submit button can remain disabled and typed content can race + * with stale Livewire responses. + */ + public function test_ticket_create_editor_does_not_live_sync_on_each_keypress(): void + { + $editor = file_get_contents(base_path('Modules/Helpdesk/Resources/views/components/html-editor.blade.php')); + + $this->assertIsString($editor); + $this->assertStringNotContainsString('x-on:keyup="sync"', $editor); + $this->assertStringNotContainsString('this.$wire.set(this.model, this.value);', $editor); + $this->assertStringContainsString('x-on:input.debounce.250ms="sync(false)"', $editor); + $this->assertStringContainsString('autocorrect="off"', $editor); + $this->assertStringContainsString('autocomplete="off"', $editor); + $this->assertStringContainsString('spellcheck="true"', $editor); } } diff --git a/Modules/Helpdesk/changelog.yaml b/Modules/Helpdesk/changelog.yaml index 07d4484e..b0d6c1bb 100644 --- a/Modules/Helpdesk/changelog.yaml +++ b/Modules/Helpdesk/changelog.yaml @@ -228,3 +228,15 @@ - Mirrored manual time-log entries into helpdesk_ticket_time_logs when the timer history table is present so the ticket detail modal stays current. - Updated TicketTimeLogTest to use authorised Helpdesk fixture users and lookup data instead of ad-hoc users that fail the ticket update policy. - Kept the pricing-free dashboard test baseline and preserved append-only changelog history. + +- version: 1.9.51 + date: 2026-06-24 + type: bugfix + summary: Stabilise Helpdesk ticket create rich editor and single-click submit. + changes: + - Changed the reusable Helpdesk rich editor to defer Livewire body synchronisation instead of sending a server request for every keypress. + - Added a submit-capture synchronisation hook so the latest Details content is included before TicketCreate validation runs. + - Removed the duplicate keyup synchronisation path that could leave Create Ticket disabled while typing. + - Disabled browser autocorrect and autocomplete on the contenteditable editor while preserving native spellcheck support. + - Updated regression coverage for deferred editor sync, submit sync and keypress race prevention. + - Preserved append-only changelog history.