Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Nelex/3b1a83e8b60a74dc4a90022ca27a8b75 to your computer and use it in GitHub Desktop.
Save Nelex/3b1a83e8b60a74dc4a90022ca27a8b75 to your computer and use it in GitHub Desktop.

Так получилось что прикрутить один визивиг недостаточно, нужна по-любому загрузка картинок. Визуально лучшим редактором и более подходящим по возможностям мы выбрали CKEditor 5 в режиме balloon block. Чтобы было понятнее, режим balloon block это блочное редактирование прямо на странице с материалом.

Но есть проблема, конфиг прошлой версии CKEditor 4 не подходит. Вторая проблема, конфиг MyCustomUploadAdapter, который достаточно просто найти в сети, не работает. Перепробовал несколько версий, то идут версии чисто под реакт, то нерабочая, отправляющая вместо файла на сервер Promise.

В итоге, использовал тот же MyCustomUploadAdapter из сети с поправой из свежей версии CKFinder адаптера.

Сразу обозначу что код рабочий, но не чистый и добавлял его чтобы не забыть что как делал, но и возможно кому-то пригодиться. Кому нужно, код поправят под то как им нужно. Все это черновой код и продакшн код я не публикую специально. В итоге код выглядит так:

            class MyUploadAdapter {
                constructor( loader, url, t ) {
                    // CKEditor 5's FileLoader instance.
                    this.loader = loader;

                    // URL where to send files.
                    this.url = '{{route('front_uploader')}}';
                    //translate function
                    //this.t = t;
                }


                // Starts the upload process.
                upload() {
                    return this.loader.file.then( file => {
                        return new Promise( ( resolve, reject ) => {
                            this._initRequest();
                            this._initListeners( resolve, reject, file );
                            this._sendRequest( file );
                        } );
                    } );
                }

                // Aborts the upload process.
                abort() {
                    if ( this.xhr ) {
                        this.xhr.abort();
                    }
                }

                // Example implementation using XMLHttpRequest.
                _initRequest() {
                    const xhr = this.xhr = new XMLHttpRequest();

                    xhr.open( 'POST', this.url, true );
                    xhr.responseType = 'json';
                }

                // Initializes XMLHttpRequest listeners.
                _initListeners( resolve, reject, file ) {
                    console.log(file);
                    const xhr = this.xhr;
                    const loader = this.loader;
                    //const t = this.t;
                    const genericError = ( 'Cannot upload file:'  + ` ${ file.name }.`);

                    xhr.addEventListener( 'error', () => reject( genericError ) );
                    xhr.addEventListener( 'abort', () => reject() );
                    xhr.addEventListener( 'load', () => {
                        const response = xhr.response;

                        if ( !response || !response.uploaded ) {
                            console.log(response);
                            return reject( response && response.error && response.error.message ? response.error.message : genericError );
                        }

                        resolve( {
                            default: response.url
                        } );
                    } );

                    // Upload progress when it's supported.
                    /* istanbul ignore else */
                    if ( xhr.upload ) {
                        xhr.upload.addEventListener( 'progress', evt => {
                            if ( evt.lengthComputable ) {
                                loader.uploadTotal = evt.total;
                                loader.uploaded = evt.loaded;
                            }
                        } );
                    }
                }

                /**
                 * Prepares the data and sends the request.
                 *
                 * @private
                 * @param {File} file File instance to be uploaded.
                 */
                _sendRequest( file ) {
                    // Prepare form data.
                    const data = new FormData();
                    data.append( 'upload', file );
                    //data.append( 'ckCsrfToken', getCsrfToken() ); //это для тех кому важна csrf от CKEditor/CKFinder

                    // Send request.
                    this.xhr.send( data );
                }
            }

            document.addEventListener('DOMContentLoaded',function (){

                function MyCustomUploadAdapterPlugin( editor ) {
                    editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
                        return new MyUploadAdapter( loader );
                    };
                }

                const config = {

                    extraPlugins: [ MyCustomUploadAdapterPlugin ],
                    simpleUpload: {
                        uploadUrl: '/uploader' //путь куда сохраняется материал
                    }
                };
                var ta = document.querySelector( '.textarea' );

                BalloonEditor
                    .create( ta, config
                    ).then( editor => { window.editor = editor } )
                    .catch( error => {
                        console.error( error );
                    } );
                    
                    //код сохранения по клику на кнопку
                document.getElementById( 'saveArticle' ).onclick = () => {
                    $.post('{{route('admin_articles_edit',['id'=>$record->id])}}',{ //роут сохранения статьи
                        'fields[body]':editor.getData(),
                        _token: '{{ csrf_token() }}' //csrf от Laravel
                    })
                }
            });
    Бекенд на ларавеле отрабатывает приблизительно так:
<?php namespace App\Http\Controllers\Front;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;

class Fileuploader extends Controller {

    public function upload(Request $request){
        if (Auth::guest()) abort(403);
        $file = $request->file('upload');
            if ($file->isReadable()){
                $filename = md5(date("YmdHis").rand(5,50));
                $extension = $file->getClientOriginalExtension();
                Storage::disk('front_uploaded')->put($filename.'.'.$extension,  File::get($file));
                $http_path = '/images/uploaded/front/'.$filename.".".$extension;
                return ['url'=>$http_path,'file'=>['name'=>$filename,'url'=>$http_path],'uploaded'=>true];
            }
            else
            {
                return ['failed'];
            }
    }

}

Не забудьте путь добавить в исключение VerifyCsrfToken для Laravel или прикрутить нормально проверку:

protected $except = [
        'uploader',
    ];

и сам путь добавить в роутинг:

Route::group(['prefix' => 'uploader', 'nocsrf' => true], function () {
        Route::match(['get', 'post'], '/', 'Fileuploader@upload')->name('front_uploader');
    });

Протестированно на внутренних проектах https://100.ks.ua и отработало нормально

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment