Загрузка...

Laravel. Livewire 3 компонент с TinyMCE + file manager

Создание компонент с TinyMCE на Livewire и vite с файловым менеджером
laravel cover

Пример создания компонента 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>

kDNLyae

в 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>

Результат

BD7uHfZ

VI2ZpKc

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *