Django

Django mdeditorでクリップボードから画像を投稿

公開日:2022-03-23 更新日:2023-06-11

djangoサイトでマークダウンエディタを実装する際にdjango-mdeditorをよく使います。

https://github.com/pylixm/django-mdeditor

デフォルトの画像投稿は、画像アイコンをクリックして、アップロード用のダイアログを表示して行います。

このままでも事足りるのですが、最近の入力UIはクリップボードからのコピー&ペーストで画像を投稿できることが多いです。

慣れというのは怖いもので、ちょっとしたことですが、面倒だと感じる部分です。

というわけで、今回はこのエディタ上でクリップボードからの画像投稿に対応させていきます。

実装の流れ

以下のようになります。

  1. イベントから画像を取得
  2. ajaxで画像を送信
  3. エディターに書き出す

イベントから画像を取得

htmlのフォーム部分ですが、以下のようになっています。

<form method="POST" enctype="multipart/form-data">
  {% csrf_token%}
  <div class="form-group">    
    ...
    <label for="id_text" class="form-label">本文:</label>
    {{ form.text }}
  </div>
  <button class="btn btn-primary" id="js-btn" type="submit">送信</button>
</form>

{{ form.text }} となっている部分がエディターですね。

まずクリップボードから画像を取得するJavaScriptを追記します。

後にjQueryを使用するので適当なcdnを読み込みます。

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script type="text/javascript">
  // エディターのid
  $('#id_text-wmd-wrapper').on('paste', function(event) {
    // event からクリップボードのアイテムを取り出す
    const items = event.originalEvent.clipboardData.items;
    for (const item of items) {
      if (item.type.indexOf("image") != -1) {
        //画像を取得
        const file = item.getAsFile();
      }
    }
  });
</script>

以下のようにするとfileに画像データが入ります。

まず、

const items = event.originalEvent.clipboardData.items;

このようにすることで、クリップボードの値を取得しています。

後はfor文でitemsを繰り返していきます。

for (const item of items) {
      if (item.type.indexOf("image") != -1) {
        //画像を取得
        const file = item.getAsFile();
}

クリップボードのデータ、つまりitemは以下のような形式で返ります。

DataTransferItem {kind: 'file', type: 'image/png'}

ここでitem.type.indexOf ("image")とするとtypeがimageで始まらない時は-1を返します。

indexOf() メソッドは、呼び出す String オブジェクト中で、 fromIndex から検索を始め、指定された値が最初に現れたインデックスを返します。値が見つからない場合は -1 を返します。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf

なので、逆に-1じゃないときは画像データなので、取得する処理に移る、というわけです。

後は取得したファイルをもとにbackendに送信してレスポンスを書き出す、という形になります。

画像の送信、書き出し

全体を以下のようにします。

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script type="text/javascript">
  // エディターのid
  $('#id_text-wmd-wrapper').on('paste', function(event) {
    // event からクリップボードのアイテムを取り出す
    const items = event.originalEvent.clipboardData.items;
    for (const item of items) {
      if (item.type.indexOf("image") != -1) {
        //画像を取得
        const file = item.getAsFile();
        // 追記 アップする処理
        upload_file_with_ajax(file)
      }
    }
  });

  // ファイルアップロード処理
  function upload_file_with_ajax(file) {
    const formData = new FormData();
    formData.append('editormd-image-file', file);
    $.ajax('mdeditor/uploads/', {
      type: 'POST',
      contentType: false,
      processData: false,
      data: formData,
      error: function() {
        // error処理
        window.alert("画像のアップに失敗")
      },
      success: function(res) {
        //マークダウン記法
        const path = `![](${res.url})`
        const current = document.activeElement;
        if (path) {
          current.value = path
        }
      }
    });
  }
</script>

django-mdeditorが受け取れるようにajaxで送信している、ということになります。

まず、送信するデータを受け取れるようにして加えます。

formData.append('editormd-image-file', file);

ここの部分です。

django-mdeditorのviews.pyを見てみます。

class UploadView(generic.View):
	    """ upload image file """
	
	    @method_decorator(csrf_exempt)
	    def dispatch(self, *args, **kwargs):
	        return super(UploadView, self).dispatch(*args, **kwargs)
	

	    def post(self, request, *args, **kwargs):
      # ここ
	        upload_image = request.FILES.get("editormd-image-file", None)
	        media_root = settings.MEDIA_ROOT
...

このようにeditormd-image-fileというキーで画像を取得しています。

なので、クライアント側からこのキーを指定して画像ファイルを渡しています。

viewが呼ばれる送信先のurlですが、django projectのurls.pyは素直に実装していると、このようになっていると思います。

# project/urls.py
urlpatterns = [
    url(r'mdeditor/', include('mdeditor.urls')), 
]

次にdjango-mdeditorのurls.pyは以下のようになっています。

urlpatterns = [
	    url_func(r'^uploads/$', UploadView.as_view(), name='uploads'),
	]

なので、このような構成の場合、mdeditor/uploads/へ送信すると先ほど引用したviewが呼ばれる、ということになります。

後は成功したらエディターのアクティブな箇所にマークダウン記法で書き出すだけです。

success: function(res) {
        //マークダウン記法
        const path = `![](${res.url})`
        const current = document.activeElement;
        if (path) {
          current.value = path
        }
      }

ここの部分です。

試しに適当な画像をクリップボードからエディターに貼り付けてみましょう。

このように直接書き込みができました。

Twitter Share