Django

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

更新日:2022-03-23 公開日:2022-03-23

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
        }
      }


ここの部分です。

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



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