djangoサイトでマークダウンエディタを実装する際にdjango-mdeditorをよく使います。
https://github.com/pylixm/django-mdeditor
デフォルトの画像投稿は、画像アイコンをクリックして、アップロード用のダイアログを表示して行います。
このままでも事足りるのですが、最近の入力UIはクリップボードからのコピー&ペーストで画像を投稿できることが多いです。
慣れというのは怖いもので、ちょっとしたことですが、面倒だと感じる部分です。
というわけで、今回はこのエディタ上でクリップボードからの画像投稿に対応させていきます。
以下のようになります。
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
}
}
ここの部分です。
試しに適当な画像をクリップボードからエディターに貼り付けてみましょう。
このように直接書き込みができました。