Пример создания компонента livewire – визуального редактора tinyMCE с использованием возможностей alphine.js и сборщиком vite.
Laravel php component
Компонент livewire с редактором:
<?php /** * Livewire 3 component - TinyMce editor * * 1. Add component to LiveWire folder in project * 2. Register component, for example add node to AppServiceProvider boot method: Blade::component('editor', Editor::class); * 3. Register route for file uploader : * Route::middleware(['web', 'auth'])->post('/tinymce/upload', function (Request $request) { * $disk = $request->disk ?? 'public'; * $folder = $request->folder ?? 'editor'; * $file = Storage::disk($disk)->put($folder, $request->file('file'), 'public'); * $url = Storage::disk($disk)->url($file); * return ['location' => $url]; * }); * 4. Include editor in your form: <x-editor wire:model="content"></x-editor> * 5. Include tinyMCE js to <header></header> * 6. Check result https://i.imgur.com/nT4uxIj.png */ namespace App\Livewire; use Closure; use Illuminate\Contracts\View\View; use Illuminate\View\Component; class Editor extends Component { public string $uuid; public function __construct( public ?string $label = null, public ?string $hint = null, public ?string $disk = 'public', public ?string $folder = 'editor', public ?array $config = [], // Validations public ?string $errorField = null, public ?string $errorClass = 'text-red-500 label-text-alt p-1', public ?bool $omitError = false, public ?bool $firstErrorOnly = false, ) { $this->uuid = "my" . md5(serialize($this)); } public function modelName(): ?string { return $this->attributes->whereStartsWith('wire:model')->first(); } public function errorFieldName(): ?string { return $this->errorField ?? $this->modelName(); } public function setup(): string { $setup = array_merge([ /*'menubar' => false, 'automatic_uploads' => true, 'remove_script_host' => false,*/ //'height' => 300, 'quickbars_insert_toolbar' => false, 'branding' => false, 'relative_urls' => false, 'toolbar' => 'undo redo | blocks fontsize | bold italic underline strikethrough | link image media table mergetags | addcomment showcomments | spellcheckdialog a11ycheck typography | align lineheight | checklist numlist bullist indent outdent | emoticons charmap | removeformat', 'quickbars_selection_toolbar' => 'bold italic underline strikethrough | forecolor backcolor | link blockquote removeformat | blocks', ], $this->config); $setup['plugins'] = str('advlist autolink lists link image table quickbars insertdatetime media nonbreaking save table directionality')->append($this->config['plugins'] ?? ''); return str(json_encode($setup))->trim('{}')->replace("\"", "'")->toString(); } public function render(): View|Closure|string { return <<<'HTML' <div> <!-- STANDARD LABEL --> @if($label) <label for="{{ $uuid }}" class="pt-0 label label-text font-semibold"> <span> {{ $label }} @if($attributes->get('required')) <span class="text-error">*</span> @endif </span> </label> @endif <!-- EDITOR --> <div x-data=" { value: @entangle($attributes->wire('model')), uploadUrl: '/file/upload?disk={{ $disk }}&folder={{ $folder }}&_token={{ csrf_token() }}' }" x-init=" tinymce.init({ {{ $setup() }}, target: $refs.tinymce, readonly: {{ json_encode($attributes->get('readonly') || $attributes->get('disabled')) }}, @if($attributes->get('disabled')) content_style: 'body { opacity: 50% }', @else content_style: 'img { max-width: 100%; height: auto; }', @endif setup: function(editor) { editor.on('keyup', (e) => value = editor.getContent()) editor.on('change', (e) => value = editor.getContent()) editor.on('init', () => editor.setContent(value ?? '')) editor.on('OpenWindow', (e) => tinymce.activeEditor.topLevelWindow = e.dialog) }, file_picker_callback : function(callback, value, meta) { var x = window.innerWidth || document.documentElement.clientWidth || document.getElementsByTagName('body')[0].clientWidth; var y = window.innerHeight|| document.documentElement.clientHeight|| document.getElementsByTagName('body')[0].clientHeight; var cmsURL = '/' + 'laravel-filemanager?editor=' + meta.fieldname; if (meta.filetype == 'image') { cmsURL = cmsURL + '&type=Images'; } else { cmsURL = cmsURL + '&type=Files'; } tinyMCE.activeEditor.windowManager.openUrl({ url : cmsURL, title : 'Filemanager', width : x * 0.8, height : y * 0.8, resizable : 'yes', close_previous : 'no', onMessage: (api, message) => { callback(message.content); } }); } }) " x-on:livewire:navigating.window="tinymce.activeEditor.destroy();" wire:ignore > <input x-ref="tinymce" type="textarea" {{ $attributes->whereDoesntStartWith('wire:model') }} /> </div> <!-- ERROR --> @if(!$omitError && $errors->has($errorFieldName())) @foreach($errors->get($errorFieldName()) as $message) @foreach(Arr::wrap($message) as $line) <div class="{{ $errorClass }}" x-classes="text-red-500 label-text-alt p-1">{{ $line }}</div> @break($firstErrorOnly) @endforeach @break($firstErrorOnly) @endforeach @endif <!-- HINT --> @if($hint) <div class="label-text-alt text-gray-400 pl-1 mt-2">{{ $hint }}</div> @endif </div> HTML; } }
Регистрируем в сервис провайдере: Blade::component('editor', Editor::class);
Tinymce js
Подключаем в head js
<script src="{{ asset('/vendor/tinymce/tinymce.js') }}"></script>
в resources/js/app.js
добавляем:
document.addEventListener('livewire:navigated', () => { window.tinymce = tinymce; }, { once: true })
File manager
Подключаем файловый менеджер
composer require unisharp/laravel-filemanager
Использование в компоненте livewire
Вставляем в компонент с редактируемым контентом:
<x-editor wire:model="content" style="min-height: 540px"></x-editor>