Skip to content

Instantly share code, notes, and snippets.

@rwsite
Last active July 3, 2024 17:22
Show Gist options
  • Save rwsite/65c631d979687377b14233ece3070cc2 to your computer and use it in GitHub Desktop.
Save rwsite/65c631d979687377b14233ece3070cc2 to your computer and use it in GitHub Desktop.
Livewire 3 component - TinyMce editor
<?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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment