diff --git a/VERSION b/VERSION index b2be32b..8c6b1d1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.38 +1.3.39 diff --git a/resources/views/livewire/admin/edit-mail-template.blade.php b/resources/views/livewire/admin/edit-mail-template.blade.php index c7cf6b5..2300876 100644 --- a/resources/views/livewire/admin/edit-mail-template.blade.php +++ b/resources/views/livewire/admin/edit-mail-template.blade.php @@ -37,7 +37,11 @@ {{ __('templates.save') }} - {{ __('templates.cancel') }} + {{-- Livewire.dispatch, not Alpine's $dispatch. Alpine's fires a + browser DOM event, and the modal listens for a LIVEWIRE event of + that name — so the button did nothing at all. Every other modal + in this repo does it this way; this one was the odd one out. --}} + {{ __('templates.cancel') }} diff --git a/resources/views/livewire/admin/mail-templates.blade.php b/resources/views/livewire/admin/mail-templates.blade.php index fd29e85..6f59c92 100644 --- a/resources/views/livewire/admin/mail-templates.blade.php +++ b/resources/views/livewire/admin/mail-templates.blade.php @@ -1,110 +1,137 @@ -
-
-

{{ __('templates.title') }}

-

{{ __('templates.subtitle') }}

+{{-- + Thirteen answers on one screen. + + The first version was one column: name, subject, two clamped lines of body + and two full-width buttons per entry. Thirteen of those is four screens of + scrolling to find the one you want, and the body preview was the least + useful part of it — the whole text is one click away in the modal anyway. + + So: two columns of one-line entries, actions as icons that do not compete + with the name, the create form folded away behind a button, and the + placeholder list as a compact grid rather than a tall sidebar that pushed the + entries into half the window. +--}} +
+
+
+

{{ __('templates.title') }}

+

{{ __('templates.subtitle') }}

+
+ + {{ __('templates.new') }} +
-
-
- {{-- The list. Order is the operator's own, because the order they - think in is neither alphabetical nor the order these happened to - be written in. --}} -
-

{{ __('templates.list') }}

- - @if ($templates->isEmpty()) -

{{ __('templates.empty') }}

- @else -
    - @foreach ($templates as $template) -
  • -
    - $template->active, 'text-muted line-through' => ! $template->active])> - {{ $template->name }} - - @if (! $template->active) - {{ __('templates.retired') }} - @endif - - {{-- Two clicks, one value each, no height - change: R20's exception. --}} - - - -
    - -

    {{ $template->subject }}

    -

    {{ $template->body }}

    - -
    - {{-- R20: editing opens a modal. A five-line - textarea in a list row is the height jump - the rule exists to stop. --}} - - {{ __('templates.edit') }} - - - {{ $template->active ? __('templates.retire') : __('templates.restore') }} - -
    -
  • - @endforeach -
- @endif -
- - {{-- Creating. A form that IS the page rather than a modal — R20 - exempts exactly this, because nothing is being edited in place. --}} -
-

{{ __('templates.new') }}

- -
- - -
- -
- - - @error('body')

{{ $message }}

@enderror -
- - - {{ __('templates.create') }} - -
+ {{-- Folded away by default. A form that is always open is a screen of form + between the operator and the thing they came for — and creating a + template is the rarer of the two things done here. It stays a form that + IS the page (R20 exempts exactly that); only its visibility changed. --}} +
+
+ +
- {{-- The placeholders, rendered from the renderer's own list so a token - added there appears here without anybody writing it down twice. --}} -
+
+ + + @error('body')

{{ $message }}

@enderror +
+ +
+ + {{ __('templates.create') }} + + {{ __('templates.cancel') }} +
+ + + {{-- The placeholders, as a grid across the page rather than a column beside + it. Rendered from the renderer's own list, so a token added there + appears here without being written down twice. --}} +
+

{{ __('templates.placeholders') }}

-

{{ __('templates.placeholders_note') }}

- -
- @foreach ($placeholders as $token => $explanation) - {{-- Assembled in PHP, not inline: Blade's echo tag ends at - the first }} it finds, so a literal one inside the - expression cuts the tag in half and compiles to - nonsense. Caught by the test that renders this page. --}} - @php $literal = '{{'.$token.'}}'; @endphp -
-
{{ $literal }}
-
{{ __($explanation) }}
-
- @endforeach -
- -

{{ __('templates.unknown_note') }}

+

{{ __('templates.placeholders_note') }}

+ +
+ @foreach ($placeholders as $token => $explanation) + {{-- Assembled in PHP, not inline: Blade's echo tag ends at the + first }} it finds, so a literal one inside the expression + cuts the tag in half and compiles to nonsense. --}} + @php $literal = '{{'.$token.'}}'; @endphp +
+
{{ $literal }}
+
{{ __($explanation) }}
+
+ @endforeach +
+ +

{{ __('templates.unknown_note') }}

+ + {{-- The entries. Two columns, one line each: what an operator scans for is + the name, and the subject is the only other thing that tells two similar + ones apart. The text itself is in the modal. --}} + @if ($templates->isEmpty()) +
+

{{ __('templates.empty') }}

+
+ @else +
+ @foreach ($templates as $template) +
$template->active, + // A retired one stays visible and stays quiet. + 'border-line bg-surface-2' => ! $template->active, + ])> +
+
+ $template->active, + 'text-muted line-through' => ! $template->active, + ])>{{ $template->name }} + @if (! $template->active) + {{ __('templates.retired') }} + @endif +
+

{{ $template->subject }}

+
+ + {{-- Icons, and one word for the state change. Two full-width + buttons per entry was most of the height. --}} +
+ + + + {{-- R20: editing opens a modal. A five-line textarea in a + list row is the height jump the rule exists to stop. --}} + +
+
+ @endforeach +
+ @endif
diff --git a/tests/Feature/EditInModalTest.php b/tests/Feature/EditInModalTest.php index 2b7187a..708db70 100644 --- a/tests/Feature/EditInModalTest.php +++ b/tests/Feature/EditInModalTest.php @@ -76,3 +76,35 @@ it('reaches every edit modal through openModal', function () { expect($users)->toContain("component: 'edit-seat'"); }); + +it('closes every modal through the event the modal actually listens for', function () { + // Alpine's $dispatch fires a browser DOM event; wire-elements/modal listens + // for a LIVEWIRE event of that name. A cancel button written the Alpine way + // therefore does nothing at all — reported on the mail-template modal, where + // saving worked and cancelling did not. Every other modal in the repo was + // already correct, which is exactly why nobody noticed the odd one out. + $offenders = collect(File::allFiles(resource_path('views'))) + ->filter(fn ($file) => str_ends_with($file->getFilename(), '.blade.php')) + ->filter(function ($file) { + // Blade comments stripped first: this file's own explanation of the + // wrong form must not read as an offence. + $body = preg_replace('/\{\{--.*?--\}\}/s', '', $file->getContents()); + + // ALPINE's $dispatch only. wire:click="$dispatch('closeModal')" is + // Livewire's own dispatcher and works — the broken form is the one + // bound through an Alpine handler, where $dispatch means the DOM. + return preg_match( + '/(?:x-on:click|@click)\s*=\s*"[^"]*\$dispatch\(\s*[\'"]closeModal/', + (string) $body, + ) === 1; + }) + ->map(fn ($file) => $file->getRelativePathname()) + ->values() + ->all(); + + expect($offenders)->toBe([]); + + // And the working form is the one in use. + expect(File::get(resource_path('views/livewire/admin/edit-mail-template.blade.php'))) + ->toContain("Livewire.dispatch('closeModal')"); +});