laravelでwordpressライクなエディタ tinymce

## やりたい事
こんな感じのwordpressライクなエディタを搭載したい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3686263/62ae68bc-0ffb-70e8-8b43-d2cd9ec11b7c.png)

## blade
これでツールバー付きのメニューバーができる。
ドラック＆ドロップで画像を登録できます。
```php: blade
{{ Form::textarea('hoge', null) }}

//....

<script src="/js/tinymce/tinymce.min.js"></script> 

<script>
    tinymce.init({
        selector: 'textarea',
        language: 'ja',
        icons: 'thin',
        statusbar: false,
        menubar: false,
        plugins: 'anchor autolink charmap codesample emoticons image link lists media searchreplace table visualblocks wordcount code',
        // ツールバーは好みにカスタマイズ
        // toolbar: 'blocks fontsize bold italic underline strikethrough forecolor removeformat | alignleft aligncenter alignright alignjustify | emoticons link image table | numlist bullist | code',
        toolbar: 'fontsize bold underline strikethrough forecolor removeformat alignleft aligncenter alignright table emoticons link image code',
        block_formats: 'Paragraph=p; Header 1=h3; Header 2=h4; Header 3=h5',
        color_map: [
            '#BFEDD2', 'Light Green',
            '#FBEEB8', 'Light Yellow',
            '#F8CAC6', 'Light Red',
            '#ECCAFA', 'Light Purple',
            '#C2E0F4', 'Light Blue',
            
            '#2DC26B', 'Green',
            '#F1C40F', 'Yellow',
            '#E03E2D', 'Red',
            '#B96AD9', 'Purple',
            '#3598DB', 'Blue',
            
            '#169179', 'Dark Turquoise',
            '#E67E23', 'Orange',
            '#BA372A', 'Dark Red',
            '#843FA1', 'Dark Purple',
            '#236FA1', 'Dark Blue',
            
            '#ECF0F1', 'Light Gray',
            '#CED4D9', 'Medium Gray',
            '#95A5A6', 'Gray',
            '#7E8C8D', 'Dark Gray',
            '#34495E', 'Navy Blue',
            
            '#000000', 'Black',
            '#ffffff', 'White',
        ],

        image_title: true,
        relative_urls: false,
        automatic_uploads: true,
        // 画像が貼り付けられるとアクセスするリンク
        images_upload_url: '/tinymce?_token={{csrf_token()}}&folder=fugu&id={{$id}}', 
        file_picker_types: 'image',
        file_picker_callback: function(cb, value, meta) {
            var input = document.createElement('input');
            input.setAttribute('type', 'file');
            input.setAttribute('accept', 'image/*');
            input.onchange = function() {
                var file = this.files[0];

                var reader = new FileReader();
                reader.readAsDataURL(file);
                reader.onload = function () {
                    var id = 'blobid' + (new Date()).getTime();
                    var blobCache =  tinymce.activeEditor.editorUpload.blobCache;
                    var base64 = reader.result.split(',')[1];
                    var blobInfo = blobCache.create(id, file, base64);
                    blobCache.add(blobInfo);
                    cb(blobInfo.blobUri(), { title: file.name });
                };
            };
            input.click();
        }
    });
</script>

```

## Services追加
ドラック＆ドロップされた画像がajaxで送られてくるので受け取ってstrageに保存する。
tinymceで削除された画像をstrageから消す。

```php: app/Services/ImageService.php
<?php

namespace App\Services;

use App\Http\Requests\ProfileUpdateRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\View\View;
use Illuminate\Support\Facades\Storage;

class ImageService
{
    //tinymceから送られた画像を保存
    public static function tinymce(Request $request) 
    {
        $folder = $request->input()['folder'].'/'.$request->input()['id'];
        $file = $request->file('file');
        $extension = $file->getClientOriginalExtension();

        $fileName = 'tiro_tool_'.time().'.'.$extension;
        $directoryPath = storage_path('app/public/' . $folder);
        // フォルダが存在しない場合は作成
        if (!file_exists($directoryPath)) {
            mkdir($directoryPath, 0777, true);
        }
        chmod($directoryPath, 0777);
        $file->storeAs('public/'.$folder.'/', $fileName);
        return response()->json(['location'=>'/storage/'.$folder.'/'.$fileName]);
    }

    //tinymceで使われていない画像を削除
    public static function tinymceClear($folder, $instance) 
    {
        $folder .= '/'.$instance->id.'/';
        if ($folder[0] !== '/') $folder = '/' . $folder;
        $useingList = [];
        $folderNeedle = 'public'.$folder;
        $urlNeedle = '/storage'.$folder;
        $nameNeedle = 'tiro_tool_';
        //テーブルのカラム一覧を取得
        $columns = $instance->getConnection()->getSchemaBuilder()->getColumnListing($instance->getTable());
        foreach($columns as $c){
            $text = $instance->$c;
            $arr = explode($urlNeedle, $text);
            foreach($arr as $a){
                if(strpos($a, $nameNeedle)===0){
                    $useingList[] = (explode('"', $a))[0];
                }
            }
        }
        $files = Storage::files('public'.$folder);
        foreach ($files as $file) {
            $fileName = str_replace($folderNeedle, '', $file);
            if(!in_array($fileName, $useingList)){
                Storage::delete($file);
            }
        }
    }
}
```

## ルーティング
```php: web.php
use App\Services\ImageService;

Route::post('/tinymce', [ImageService::class, 'tinymce']);
```