ehyiah/ux-quill
Symfony UX bundle integrating the Quill.js WYSIWYG editor. Drop-in QuillType form field with multiple editors per page, works with AssetMapper or Webpack Encore, and includes a Twig component to render saved content with optional inline styling.
## Getting Started
### Minimal Steps for Laravel Integration
Since `ux-quill` is a Symfony UX bundle, Laravel developers will need to adapt it using **Symfony UX components** or **Laravel's bridge packages**. Here’s how to get started:
1. **Install Required Packages**
```bash
composer require symfony/ux symfony/ux-live-component symfony/ux-turbo symfony/ux-stimulus-bundle
composer require ehyiah/ux-quill
For AssetMapper (recommended for Laravel 9+):
composer require symfony/asset-mapper
Configure AssetMapper (Laravel 9+)
Add to config/app.php:
'asset_mapper' => [
'bundles' => [
Ehyiah\QuillJsBundle\QuillJsBundle::class,
],
],
Basic Form Usage In a Laravel FormRequest or FormBuilder:
use Ehyiah\QuillJsBundle\Form\QuillType;
use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('content', QuillType::class, [
'label' => 'Article Content',
'preset' => 'default', // Uses Quill's default toolbar
'attr' => ['class' => 'quill-editor'],
]);
}
Render in Blade
Use the Twig component via Laravel’s Twig integration (if using laravel-bridge):
@php
echo $this->renderComponent('quill_content_styles');
@endphp
<x-twig:QuillContent value="{{ $post->content }}" />
Alternative (inline mode):
<div>{{ Illuminate\Support\HtmlString::from($post->content) }}</div>
Webpack Encore Setup (if not using AssetMapper)
Add to webpack.config.js:
Encore
.addEntry('quill', './vendor/ehyiah/ux-quill/resources/assets/js/quill')
.enableStimulusBridge()
.copyFiles({
from: './vendor/ehyiah/ux-quill/resources/public',
to: 'build/[path][name].[ext]',
});
Run:
npm run dev
Use QuillType with Laravel’s FormBuilder (via laravelcollective/html):
use Ehyiah\QuillJsBundle\Form\QuillType;
use Collective\Html\FormBuilder;
Form::model($post, [
'method' => 'PUT',
'url' => route('posts.update', $post),
])->add('content', QuillType::class, [
'quill_options' => [
'theme' => 'bubble',
'modules' => ['history'],
],
]);
For dynamic updates, use QuillType in a Livewire component:
use Livewire\Component;
use Ehyiah\QuillJsBundle\Form\QuillType;
use Symfony\Component\Form\FormBuilderInterface;
class PostEditor extends Component {
public $content;
public function mount() {
$this->content = '<p>Hello, Quill!</p>';
}
public function buildForm(FormBuilderInterface $builder) {
$builder->add('content', QuillType::class, [
'mapped' => false,
'data' => $this->content,
'quill_options' => ['placeholder' => 'Write your post...'],
]);
}
public function updated($property) {
$this->content = $this->form->get('content')->getData();
}
public function render() {
return view('livewire.post-editor');
}
}
Blade Template:
@livewire('post-editor', key($post->id))
{{ quill_content_styles() }}
Configure the upload endpoint in QuillType and handle uploads via Laravel routes:
$builder->add('article', QuillType::class, [
'upload_handler' => [
'upload_endpoint' => route('quill.upload'),
'headers' => ['X-CSRF-TOKEN' => csrf_token()],
],
]);
Route Definition (routes/web.php):
Route::post('/quill/upload', [QuillUploadController::class, 'upload'])->name('quill.upload');
Controller:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class QuillUploadController extends Controller {
public function upload(Request $request) {
$file = $request->file('file');
$path = $file->store('quill-uploads', 'public');
return response()->json([
'link' => Storage::url($path),
]);
}
}
Extend Quill’s functionality by adding custom modules. For PHP-based modules (e.g., history):
$builder->add('document', QuillType::class, [
'modules' => [
'history' => [
'delay' => 1000,
'maxStack' => 50,
],
],
]);
For JavaScript-based modules (e.g., clipboard), extend the Stimulus controller:
// resources/js/quill-custom.js
import { Controller } from '@hotwired/stimulus';
import { QuillController } from '@ehyiah/ux-quill';
export default class extends QuillController {
connect() {
super.connect();
this.quillOptions.modules.clipboard = true;
}
}
Register in app.js:
import './quill-custom';
Use Laravel’s built-in sanitizer (no extra dependency):
use Illuminate\Support\Str;
$sanitizedContent = Str::of($request->input('content'))
->replaceMatches('/<script\b[^>]*>(.*?)<\/script>/is', '')
->replaceMatches('/<style\b[^>]*>(.*?)<\/style>/is', '');
Or use Symfony’s sanitizer (if using symfony/ux):
use Symfony\Component\Security\Censor\CensorInterface;
$sanitized = $censor->censor($request->input('content'));
Asset Loading Failures
Uncaught ReferenceError: Quill is not defined.quill_content_styles() in Blade (AssetMapper).quill_content_styles() is called before the editor renders.npm run dev output for errors. Ensure quill entry is included in webpack.config.js.php artisan cache:clear
php artisan view:clear
Double Sanitization Issues
<table>, <img>) are stripped or malformed.Str::of() or Blade auto-escaping conflicts with Quill’s HTML.{!! $post->content !!}
Str::of):
$sanitizer = app(Symfony\Component\Security\Censor\CensorInterface::class);
$cleanContent = $sanitizer->censor($request->input('content'));
Table Module Not Working
modules: { table: true } is set in quill_options.<div class="ql-snow"> in Blade. Let Quill handle it:
<div>{{ $post->content }}</div> {# No wrapper needed #}
Stimulus Controller Conflicts
How can I help you explore Laravel packages today?