diff --git a/README.md b/README.md index 4cb087b..823753b 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,6 @@ Publish the config file: php artisan vendor:publish --tag="ozu-client-config" ``` -> [!IMPORTANT] -> As our beta builder only works with PHP 8.4 for now, your project must use PHP 8.4. - ## Usage ### Models are Ozu collections @@ -109,6 +106,49 @@ class Project extends Model > [!NOTE] > This configuration will be used by Ozu to properly display the collection in the content management tool. It has no effect in your local codebase. +### Configure your content editor +```php + public static function configureOzuCollectionForm(OzuCollectionFormConfig $config): OzuCollectionFormConfig + { + return $config + ->configureContentField(fn(OzuEditorField $field) => $field + ->setLabel('Contenu') + ->setToolbar([ + OzuEditorToolbarEnum::Heading1, + OzuEditorToolbarEnum::Heading2, + OzuEditorToolbarEnum::Separator, + OzuEditorToolbarEnum::Bold, + OzuEditorToolbarEnum::Italic, + OzuEditorToolbarEnum::Link, + OzuEditorToolbarEnum::Image, + OzuEditorToolbarEnum::Separator, + OzuEditorToolbarEnum::Iframe, + OzuEditorToolbarEnum::Quote, + OzuEditorToolbarEnum::Video, + OzuEditorToolbarEnum::Separator, + OzuEditorToolbarEnum::BulletList, + OzuEditorToolbarEnum::OrderedList, + OzuEditorToolbarEnum::Separator, + ]) + ->setMaxFileSize(12) + ->setCropRatio('1:1') + ); + } +``` + +Ozu uses a supercharged version of the sharp editor field. You can declare multiple toolbar element to enrich your content, including : + +- `OzuEditorToolbarEnum::Image` to embed images directly inside your content +- `OzuEditorToolbarEnum::Quote` to embed nice quotes with an (optional) author +- `OzuEditorToolbarEnum::Video` to embed videos from different providers (Youtube, Vimeo, Dailymotion) + +Ozu will render very basically theses content but you can override the views by publishing them with: +```bash +php artisan vendor:publish --tag="ozu-views" +``` +> [!WARNING] +> To allow ozu to renders rich content in your front, please use the `` component. + ### Handle `BelongsTo` relationships A common use case is to have a `BelongsTo` relationship between two Ozu Models. There are two possibilities: diff --git a/composer.json b/composer.json index 3e6a78f..6ed0c87 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ ], "require": { "php": "8.4.*", + "code16/embed": "^2.3", "code16/laravel-content-renderer": "^1.1.0", "guzzlehttp/guzzle": "^7.5", "illuminate/contracts": "^11.0|^12.0", diff --git a/resources/views/components/embedded-image.blade.php b/resources/views/components/embedded-image.blade.php new file mode 100644 index 0000000..1f1ddc2 --- /dev/null +++ b/resources/views/components/embedded-image.blade.php @@ -0,0 +1,14 @@ +@php use Code16\OzuClient\Eloquent\Media; @endphp +@props([ + /** @var Media $fileModel */ + 'fileModel', + 'legend' => null, +]) +
+ + @if($legend) +
+ {{ $legend }} +
+ @endif +
diff --git a/resources/views/components/quote.blade.php b/resources/views/components/quote.blade.php new file mode 100644 index 0000000..1387c2c --- /dev/null +++ b/resources/views/components/quote.blade.php @@ -0,0 +1,12 @@ +@props([ + 'quote', + 'author' => null, +]) +
+ {{$quote}} + @if($author) + + {{$author}} + + @endif +
diff --git a/resources/views/components/video.blade.php b/resources/views/components/video.blade.php new file mode 100644 index 0000000..a52a343 --- /dev/null +++ b/resources/views/components/video.blade.php @@ -0,0 +1,14 @@ +@props([ + 'url', + 'legend' => null, +]) +
+ + @if($legend) +

{{$legend}}

+ @endif +
diff --git a/src/Exceptions/OzuClientException.php b/src/Exceptions/OzuClientException.php new file mode 100644 index 0000000..ff14758 --- /dev/null +++ b/src/Exceptions/OzuClientException.php @@ -0,0 +1,7 @@ +withoutParagraphs = true; @@ -49,6 +55,28 @@ public function setHeight(int $height, ?int $maxHeight = null): self return $this; } + public function setMaxFileSize(int $maxFileSize): self + { + if (! in_array(OzuEditorToolbarEnum::Image, $this->toolbar)) { + throw new OzuClientException('You should allow Image Uploads by adding OzuEditorToolbarEnum::Image in toolbar configuration before setting max file size'); + } + + $this->maxFileSize = $maxFileSize; + + return $this; + } + + public function setCropRatio(string $cropRatio): self + { + if (! in_array(OzuEditorToolbarEnum::Image, $this->toolbar)) { + throw new OzuClientException('You should allow Image Uploads by adding OzuEditorToolbarEnum::Image in toolbar configuration before setting image crop ratio'); + } + + $this->cropRatio = $cropRatio; + + return $this; + } + public function type(): string { return 'editor'; @@ -62,6 +90,8 @@ public function toArray(): array 'toolbar' => collect($this->toolbar)->map(fn ($item) => $item->value)->toArray(), 'height' => $this->height, 'maxHeight' => $this->maxHeight, + 'maxFileSize' => $this->maxFileSize, + 'cropRatio' => $this->cropRatio, ]); } } diff --git a/src/OzuCms/Form/OzuEditorToolbarEnum.php b/src/OzuCms/Form/OzuEditorToolbarEnum.php index 1b51b8e..7f9c605 100644 --- a/src/OzuCms/Form/OzuEditorToolbarEnum.php +++ b/src/OzuCms/Form/OzuEditorToolbarEnum.php @@ -14,4 +14,7 @@ enum OzuEditorToolbarEnum: string case Heading1 = 'heading-1'; case Heading2 = 'heading-2'; case Iframe = 'iframe'; + case Image = 'upload-image'; + case Video = 'video'; + case Quote = 'blockquote'; } diff --git a/src/OzuServiceProvider.php b/src/OzuServiceProvider.php index c23a0d8..530fbbc 100644 --- a/src/OzuServiceProvider.php +++ b/src/OzuServiceProvider.php @@ -9,8 +9,11 @@ use Code16\OzuClient\Support\Thumbnails\LocalThumbnail; use Code16\OzuClient\Support\Thumbnails\Thumbnail; use Code16\OzuClient\View\Components\Content; +use Code16\OzuClient\View\Components\EmbeddedImage; use Code16\OzuClient\View\Components\File; use Code16\OzuClient\View\Components\Image; +use Code16\OzuClient\View\Components\Quote; +use Code16\OzuClient\View\Components\Video; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\Paginator; @@ -70,12 +73,18 @@ public function boot() $this->publishes([ __DIR__.'/../resources/views/components/file.blade.php' => resource_path('views/vendor/ozu/components/file.blade.php'), __DIR__.'/../resources/views/components/image.blade.php' => resource_path('views/vendor/ozu/components/image.blade.php'), + __DIR__.'/../resources/views/components/embedded-image.blade.php' => resource_path('views/vendor/ozu/components/embedded-image.blade.php'), + __DIR__.'/../resources/views/components/video.blade.php' => resource_path('views/vendor/ozu/components/video.blade.php'), + __DIR__.'/../resources/views/components/quote.blade.php' => resource_path('views/vendor/ozu/components/quote.blade.php'), ], 'ozu-views'); Blade::componentNamespace('Code16\\OzuClient\\View\\Components\\Content', 'ozu-content'); Blade::component(Content::class, 'ozu-content'); Blade::component(Image::class, 'ozu-image'); + Blade::component(EmbeddedImage::class, 'sharp-image'); Blade::component(File::class, 'ozu-file'); + Blade::component(Video::class, 'ozu-content-video'); + Blade::component(Quote::class, 'ozu-content-quote'); Paginator::currentPageResolver(function () { return request()->route()->parameter('page'); diff --git a/src/Support/Database/OzuSeeder.php b/src/Support/Database/OzuSeeder.php index 1fd4d59..1092b03 100644 --- a/src/Support/Database/OzuSeeder.php +++ b/src/Support/Database/OzuSeeder.php @@ -2,6 +2,7 @@ namespace Code16\OzuClient\Support\Database; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Seeder; class OzuSeeder extends Seeder @@ -16,4 +17,47 @@ protected function clearMediaDirectory(): void ->each(fn ($file) => unlink($mediaDirectory.'/'.$file)); } } + + public function seedVideoEmbed(Model &$model, string $editorColumnName, ?string $videoUrl = null, ?string $legend = null): void + { + $editorContent = $model->$editorColumnName; + $randomVideoUrl = collect([ + 'https://www.youtube.com/watch?v=lXKDu6cdXLI', + 'https://www.youtube.com/watch?v=ZBYZHeB67O4', + 'https://www.youtube.com/watch?v=7nQ2oiVqKHw', + ])->random(); + + $videoTag = sprintf( + '', + $videoUrl ?? $randomVideoUrl, + $legend ? sprintf('legend="%s"', $legend) : '' + ); + + $model->$editorColumnName = $editorContent.' '.$videoTag; + if ($model->id !== null) { + $model->save(); + } + } + + public function seedQuoteEmbed(Model &$model, string $editorColumnName, ?string $quote = null, ?string $author = null): void + { + $editorContent = $model->$editorColumnName; + $randomQuote = collect([ + ['The only thing we have to fear is fear itself.', 'Franklin D. Roosevelt'], + ['I think, therefore I am.', 'René Descartes'], + ['Be the change that you wish to see in the world.', 'Gandhi'], + ['In the middle of difficulty lies opportunity.', 'Albert Einstein'], + ])->random(); + + $quoteTag = sprintf( + '', + $quote ?? $randomQuote[0], + $author ?? $randomQuote[1] + ); + + $model->$editorColumnName = $editorContent.' '.$quoteTag; + if ($model->id !== null) { + $model->save(); + } + } } diff --git a/src/View/Components/EmbeddedImage.php b/src/View/Components/EmbeddedImage.php new file mode 100644 index 0000000..3e3f900 --- /dev/null +++ b/src/View/Components/EmbeddedImage.php @@ -0,0 +1,53 @@ +file = json_decode(htmlspecialchars_decode($file), true)) { + $this->fileModel = Media::make([ + 'file_name' => $this->file['file_name'], + 'disk' => $this->file['disk'] ?? null, + 'filters' => $this->file['filters'] ?? null, + ]); + $this->disk = Storage::disk($this->fileModel->disk); + $this->exists = $this->disk->exists($this->fileModel->file_name); + $this->name = $this->file['name'] ?? basename($this->fileModel->file_name); + } + + if (! $this->thumbnailWidth && ! $this->thumbnailHeight) { + $this->thumbnailWidth = 500; + } + } + + public function render(): \Illuminate\View\View + { + if (! $this->fileModel) { + throw new OzuClientException('Unable to render embedded image: invalid file'); + } + + return view('ozu::components.embedded-image'); + } +} diff --git a/src/View/Components/Quote.php b/src/View/Components/Quote.php new file mode 100644 index 0000000..17a5204 --- /dev/null +++ b/src/View/Components/Quote.php @@ -0,0 +1,24 @@ + $this->quote, + 'author' => $this->author, + ]); + } +} diff --git a/src/View/Components/Video.php b/src/View/Components/Video.php new file mode 100644 index 0000000..16b705b --- /dev/null +++ b/src/View/Components/Video.php @@ -0,0 +1,29 @@ +url); + $this->service = ServiceFactory::getByUrl($url) ?: ServiceFactory::getFallback($url); + } + + public function render() + { + return view('ozu::components.video', [ + 'url' => $this->url, + 'legend' => $this->legend, + ]); + } +} diff --git a/tests/Unit/OzuFieldTest.php b/tests/Unit/OzuFieldTest.php index e11b2ca..d57c5df 100644 --- a/tests/Unit/OzuFieldTest.php +++ b/tests/Unit/OzuFieldTest.php @@ -102,6 +102,8 @@ ], 'height' => 200, 'maxHeight' => null, + 'maxFileSize' => 5, + 'cropRatio' => null, ]); }); @@ -132,6 +134,8 @@ ], 'height' => 200, 'maxHeight' => null, + 'maxFileSize' => 5, + 'cropRatio' => null, ]); $field->hideToolbar(); @@ -167,6 +171,8 @@ ], 'height' => 300, 'maxHeight' => 500, + 'maxFileSize' => 5, + 'cropRatio' => null, ]); });