Skip to content

Commit

Permalink
Merge pull request #100 from mostafaznv/dev
Browse files Browse the repository at this point in the history
feat: insert images directly into the editor by pasting or dragging
  • Loading branch information
mostafaznv authored Nov 23, 2023
2 parents 53a7ed9 + 35d0050 commit d48ccc3
Show file tree
Hide file tree
Showing 11 changed files with 254 additions and 30 deletions.
11 changes: 11 additions & 0 deletions config/nova-ckeditor.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,17 @@
'audio' => true
],

'image' => [
/*
* Insert images directly into the editor by pasting or dragging.
*/

'insert' => [
'types' => ['gif', 'png', 'jpg', 'jpeg', 'webp'],
'size' => 1500 // kb, nullable
]
],

'snippets' => [
['name' => 'Image', 'html' => 'ckeditor.image'],
['name' => 'Media', 'html' => 'ckeditor.media'],
Expand Down
2 changes: 1 addition & 1 deletion dist/js/field.js

Large diffs are not rendered by default.

55 changes: 28 additions & 27 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,35 @@
"prod": "mix --production"
},
"devDependencies": {
"@ckeditor/ckeditor5-alignment": "^40.0.0",
"@ckeditor/ckeditor5-autoformat": "^40.0.0",
"@ckeditor/ckeditor5-basic-styles": "^40.0.0",
"@ckeditor/ckeditor5-block-quote": "^40.0.0",
"@ckeditor/ckeditor5-code-block": "^40.0.0",
"@ckeditor/ckeditor5-dev-utils": "^39.2.0",
"@ckeditor/ckeditor5-alignment": "^40.1.0",
"@ckeditor/ckeditor5-autoformat": "^40.1.0",
"@ckeditor/ckeditor5-basic-styles": "^40.1.0",
"@ckeditor/ckeditor5-block-quote": "^40.1.0",
"@ckeditor/ckeditor5-code-block": "^40.1.0",
"@ckeditor/ckeditor5-dev-utils": "^39.2.1",
"@ckeditor/ckeditor5-dev-webpack-plugin": "^31.1.13",
"@ckeditor/ckeditor5-editor-classic": "^40.0.0",
"@ckeditor/ckeditor5-essentials": "^40.0.0",
"@ckeditor/ckeditor5-font": "^40.0.0",
"@ckeditor/ckeditor5-heading": "^40.0.0",
"@ckeditor/ckeditor5-horizontal-line": "^40.0.0",
"@ckeditor/ckeditor5-html-embed": "^40.0.0",
"@ckeditor/ckeditor5-html-support": "^40.0.0",
"@ckeditor/ckeditor5-image": "^40.0.0",
"@ckeditor/ckeditor5-indent": "^40.0.0",
"@ckeditor/ckeditor5-language": "^40.0.0",
"@ckeditor/ckeditor5-link": "^40.0.0",
"@ckeditor/ckeditor5-list": "^40.0.0",
"@ckeditor/ckeditor5-media-embed": "^40.0.0",
"@ckeditor/ckeditor5-paragraph": "^40.0.0",
"@ckeditor/ckeditor5-paste-from-office": "^40.0.0",
"@ckeditor/ckeditor5-remove-format": "^40.0.0",
"@ckeditor/ckeditor5-show-blocks": "^40.0.0",
"@ckeditor/ckeditor5-source-editing": "^40.0.0",
"@ckeditor/ckeditor5-table": "^40.0.0",
"@ckeditor/ckeditor5-theme-lark": "^40.0.0",
"@ckeditor/ckeditor5-ui": "^40.0.0",
"@ckeditor/ckeditor5-editor-classic": "^40.1.0",
"@ckeditor/ckeditor5-essentials": "^40.1.0",
"@ckeditor/ckeditor5-font": "^40.1.0",
"@ckeditor/ckeditor5-heading": "^40.1.0",
"@ckeditor/ckeditor5-horizontal-line": "^40.1.0",
"@ckeditor/ckeditor5-html-embed": "^40.1.0",
"@ckeditor/ckeditor5-html-support": "^40.1.0",
"@ckeditor/ckeditor5-image": "^40.1.0",
"@ckeditor/ckeditor5-indent": "^40.1.0",
"@ckeditor/ckeditor5-language": "^40.1.0",
"@ckeditor/ckeditor5-link": "^40.1.0",
"@ckeditor/ckeditor5-list": "^40.1.0",
"@ckeditor/ckeditor5-media-embed": "^40.1.0",
"@ckeditor/ckeditor5-paragraph": "^40.1.0",
"@ckeditor/ckeditor5-paste-from-office": "^40.1.0",
"@ckeditor/ckeditor5-remove-format": "^40.1.0",
"@ckeditor/ckeditor5-show-blocks": "^40.1.0",
"@ckeditor/ckeditor5-source-editing": "^40.1.0",
"@ckeditor/ckeditor5-table": "^40.1.0",
"@ckeditor/ckeditor5-theme-lark": "^40.1.0",
"@ckeditor/ckeditor5-ui": "^40.1.0",
"@ckeditor/ckeditor5-upload": "^40.1.0",
"@vue/compiler-sfc": "^3.3.4",
"cross-env": "^7.0.3",
"laravel-mix": "^6.0.49",
Expand Down
9 changes: 7 additions & 2 deletions resources/js/ckeditor/ckeditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ import ImageTextAlternative from '@ckeditor/ckeditor5-image/src/imagetextalterna
import Indent from '@ckeditor/ckeditor5-indent/src/indent';
import IndentBlock from '@ckeditor/ckeditor5-indent/src/indentblock';

// Upload
import {SimpleUploadAdapter} from '@ckeditor/ckeditor5-upload'

// Browser
import ImageBrowser from './plugins/ImageBrowser'
import VideoBrowser from './plugins/VideoBrowser'
Expand Down Expand Up @@ -84,7 +87,8 @@ export default class CkEditor extends ClassicEditorBase {
...require('./config/headings').default,
...require('./config/html').default,
...require('./config/video').default,
...require('./config/audio').default
...require('./config/audio').default,
...require('./config/simple-upload').default
}
}

Expand Down Expand Up @@ -135,7 +139,8 @@ export default class CkEditor extends ClassicEditorBase {
Indent,
IndentBlock,
ElementAddAttributes,
Clipboard
Clipboard,
SimpleUploadAdapter
]
}
}
11 changes: 11 additions & 0 deletions resources/js/ckeditor/config/simple-upload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default {
simpleUpload: {
withCredentials: true,

uploadUrl: '/nova-vendor/nova-ckeditor/image',

headers: {
'X-CSRF-TOKEN': Nova.request().defaults.headers.common['X-CSRF-TOKEN']
}
}
}
8 changes: 8 additions & 0 deletions resources/js/components/editor-field.vue
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ export default {
items: this.currentField.toolbar,
shouldNotGroupWhenFull: this.currentField.shouldNotGroupWhenFull
},
simpleUpload: {
...CkEditor.defaultConfig.simpleUpload,
headers: {
...CkEditor.defaultConfig.simpleUpload.headers,
'X-Toolbar': this.currentField.toolbarName
},
},
...toolbarOptions
}
Expand Down
11 changes: 11 additions & 0 deletions routes/api.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

use Illuminate\Support\Facades\Route;
use Mostafaznv\NovaCkEditor\Http\Controllers\Api\UploadImageController;


Route::as('nova-ckeditor.')->middleware('nova')->group(function () {
Route::prefix('image')->name('image.')->group(function () {
Route::post('/', UploadImageController::class)->name('upload');
});
});
10 changes: 10 additions & 0 deletions src/CkEditor.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ class CkEditor extends Field
*/
public $component = 'ckeditor';

/**
* Specifies the toolbar name
*
* @var string $toolbarName
*/
public string $toolbarName;

/**
* Specifies the available toolbar items
*
Expand Down Expand Up @@ -293,6 +300,7 @@ public function snippets(array $snippets): self
public function jsonSerialize(): array
{
return array_merge(parent::jsonSerialize(), [
'toolbarName' => $this->toolbarName,
'snippetBrowser' => $this->snippetBrowser,
'imageBrowser' => $this->imageBrowser,
'videoBrowser' => $this->videoBrowser,
Expand Down Expand Up @@ -384,6 +392,8 @@ private function isLegacyNovaVideo(): bool

private function prepareToolbar(string $toolbar, array $items = null): void
{
$this->toolbarName = $toolbar;

$toolbar = config('nova-ckeditor.toolbars.' . $toolbar);

$defaultTextPartLanguage = [
Expand Down
17 changes: 17 additions & 0 deletions src/FieldServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
namespace Mostafaznv\NovaCkEditor;

use Composer\InstalledVersions;
use Illuminate\Support\Facades\Route;
use Laravel\Nova\Nova;
use Illuminate\Support\Facades\App;
use Laravel\Nova\Events\ServingNova;
use Illuminate\Support\ServiceProvider;


class FieldServiceProvider extends ServiceProvider
{
public function register(): void
Expand Down Expand Up @@ -43,6 +45,10 @@ public function boot(): void
Nova::script('field-ckeditor', __DIR__ . '/../dist/js/field.js');
}
});

$this->app->booted(function() {
$this->routes();
});
}

protected function publish(): void
Expand Down Expand Up @@ -74,6 +80,17 @@ protected function publish(): void
], 'nova-ckeditor-stubs');
}

private function routes(): void
{
if ($this->app->routesAreCached()) {
return;
}

Route::middleware(['nova:api'])
->prefix('nova-vendor/nova-ckeditor')
->group(__DIR__ . '/../routes/api.php');
}

private function registerUiLanguageScripts(): void {
$toolbars = config('nova-ckeditor.toolbars');
$scripts = [];
Expand Down
10 changes: 10 additions & 0 deletions src/Http/Controllers/Api/ApiController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Mostafaznv\NovaCkEditor\Http\Controllers\Api;

use Illuminate\Routing\Controller;


class ApiController extends Controller
{
}
140 changes: 140 additions & 0 deletions src/Http/Controllers/Api/UploadImageController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php

namespace Mostafaznv\NovaCkEditor\Http\Controllers\Api;


use App\Nova\Resources\Image;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Mostafaznv\Larupload\Traits\Larupload;
use Mostafaznv\NovaCkEditor\ImageUpload;


class UploadImageController extends ApiController
{
private string $model;
private string $toolbar;


public function __construct()
{
$this->model = config('nova-ckeditor.image-model', 'App\Models\Image');
}


public function __invoke(Request $request): JsonResponse
{
$this->toolbar = $request->header('X-Toolbar');

if ($error = $this->validate($request)) {
return $this->errorResponse($error);
}

if (!$this->uploadIsEnabled()) {
$message = __('Upload is not enabled.');

return $this->errorResponse($message, 403);
}


return response()->json([
'url' => $this->hasLaruploadTrait()
? $this->uploadWithLarupload($request)
: $this->uploadWithCustomStorage($request)
]);
}


private function validate(Request $request): ?string
{
$errors = Validator::make($request->all(), $this->rules())->errors();

if ($errors->has('upload')) {
return implode(', ', $errors->get('upload'));
}

return null;
}

private function rules(): array
{
$options = config("nova-ckeditor.toolbars.$this->toolbar.image.insert");
$maxSize = 1500;
$mimeTypes = 'jpg,jpeg,png,gif,webp';

if ($this->toolbar and $options) {
$maxSize = $options['size'];
$mimeTypes = implode(',', $options['types']);
}

return [
'upload' => [
'required', 'file', "mimes:$mimeTypes", "max:$maxSize"
],
];
}

private function uploadIsEnabled(): bool
{
return config("nova-ckeditor.toolbars.$this->toolbar.browser.image", false);
}

private function hasLaruploadTrait(): bool
{
return class_exists($this->model) and in_array(Larupload::class, class_uses($this->model));
}

private function uploadWithCustomStorage(Request $request): ?string
{
$disk = 'image';
$model = $this->model;
$file = $request->file('upload');

foreach (Image::make()->fields(request()) as $field) {
if (is_a($field, ImageUpload::class) or $field->component === 'file-field') {
$disk = $field->disk;

break;
}
}


$storage = app('ckeditor-image-storage', compact('disk'));
$upload = $storage->handleUpload($file);

$model = new $model;
$model->name = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
$model->file = $upload['file'];
$model->disk = $upload['disk'];
$model->mime = $upload['mime'];
$model->width = $upload['width'];
$model->height = $upload['height'];
$model->size = $upload['size'];
$model->save();

return $storage->url($upload['file']);
}

private function uploadWithLarupload(Request $request): ?string
{
$model = $this->model;
$file = $request->file('upload');

$model = new $model;
$model->attachment('file')->attach($file);
$model->save();


return $model->attachment('file')->url();
}

private function errorResponse(string $error, int $status = 422): JsonResponse
{
return response()->json([
'error' => [
'message' => $error
]
], $status);
}
}

0 comments on commit d48ccc3

Please sign in to comment.