Rails6でドラッグ&ドロップ可能なマークダウンエディタを作成する(Active StorageとSimpleMDEとInline Attachmentを使用)

# 概要

Rails6でQiitaの投稿画面のように、画像をドラッグ&ドロップ可能なMarkdownエディタを作成した際のメモです。

![img.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149198/9a3ed643-c396-1c0b-b5cb-be8497e39954.gif)

[参考コードはこちら](https://github.com/nobu17/rails_markdown_drag_and_drop)

ちなみにRailsをGWに学んで、このエディタ機能を実装したblogを作成しました。
(Heroku,GCSで構築)
地元の静岡県東部のスポット等を紹介していますので興味があればどうぞ。
[駿河部](https://surugabu.com)

# 環境

Mac OS 10.15.4
Ruby 2.6.3p62
Rails 6.0.2.2

# 使用する機能について

主に3つのOSS及び機能を使用して実装をしています。

- SimpleMDE
- Inline Attachment
- Active Storage

## SimpleMDE

ブラウザ上のtextareaタグをMarkdownエディタを変えてくれる素晴らしいOSS。
自動保存機能など様々な機能も搭載されています。

[リンク](https://simplemde.com)

## Inline Attachment

ブラウザ上からドラッグ&ドロップでのAjaxでのファイル送信を簡単に実装してくれる。
SimpleMDEと組み合わせて、ドラッグ&ドロップされた画像を非同期で送信してアップロード結果をエディタに反映してくれます。

[リンク](https://github.com/Rovak/InlineAttachment)

## Active Storage

Railsに組み込まれているファイルアップロードの機能。
こいつを利用するとお手軽にファイルアップロード機能が実装可能で、AWS,Azure,GCPといったクラウドストレージへのアップロードにも対応している優れもの。

# 手順

## PJの作成と各種インストール

```bash

# railsのプロジェクト作成
rails new markdown_drag_and_drop

# active storageのインストール
rails active_storage:install
rails db:migrate

# 必要なjsモジュールのインストール
yarn add simplemde
yarn add inline-attachment
```

## turbolinksの無効化

Railsにはデフォルトで高速化のために、通常のWEBサイトをSPAのような部分的なDOM変更に無理やり置き変えるturbolinksが入っています。
しかしながら、jsでゴリゴリと実装をする上でよく悪さをするので今回は無効化しています。

```js:app/javascript/packs/application.js

// 無効化
// require("turbolinks").start()
```


```erb:app/views/layouts/application.html.erb

<!-- 'data-turbolinks-track': 'reload'を削除 -->
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_pack_tag 'application' %>
```

## モデル、コントローラ、ビューの作成

scaffoldで雛形を作成します。
contentがMarkdownを文字列として保存するフィールドとしています。

```bash
rails g scaffold article content:text
```

フォーム画面にMarkdownエディタにするtextareaを配置します。

```erb:app/views/articles/_from.html.erb
<!-- 画面で使用するjs(後で記載) -->
<%= javascript_pack_tag 'article' %>

<%= form_with(model: article, local: true) do |form| %>
 <!-- 略 -->

<div class="field">
    <%= form.label :content %>
    <%= form.text_area :content, { id: "editor"} %>
</div>

<div class="actions">
    <%= form.submit %>
</div>
<% end %>

```

画面で読み込むjsを作成する。

 - textareaのエディタ化
 - エディタ内にドラッグ&ドロップされた際にajax通信する設定

を行います。
ajaxで通信する処理はこの後作成します。

```js:app/javascript/packs/article.js

import "inline-attachment/src/inline-attachment";
import "inline-attachment/src/codemirror-4.inline-attachment";
import 'simplemde/dist/simplemde.min.css'
import SimpleMDE from "simplemde";
import Rails from '@rails/ujs'

window.onload = function () {
  // textareaをMarkdownエディタにする
  const simplemde = new SimpleMDE({
    element: document.getElementById("editor"),
  });
  // エディタに画像がドラッグ&ドロップされた際の処理
  inlineAttachment.editors.codemirror4.attach(simplemde.codemirror, {
    uploadUrl: "/articles/attach", // POSTする宛先Url
    uploadFieldName: "image", // ファイルのフィールド名(paramsで取り出す時のkey)
    allowedTypes: ['image/jpeg', 'image/png', 'image/jpg', 'image/gif'],
    extraHeaders: { "X-CSRF-Token": Rails.csrfToken() }, // CSRF対策
  });
};

```

## ファイル送信用の処理の作成

Active Storageでファイル送信を行うためのモデルを作成します。

```bash
rails g model attachment
```

```ruby:app/models/attachment.rb
class Attachment < ApplicationRecord
    has_one_attached :image
end
```

scaffoldで作成したコントローラにajax通信用の処理を追加する。
画像の保存及びURLをJSONで返します。
filenameがinlineAttachmentがデフォルトで受け取るパラメータなのでそれに合わせます。

```ruby:app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  ...
  def attach
    attachment = Attachment.create! image: params[:image]
    render json: { filename: url_for(attachment.image) }
  end
  ...
end

```

ルーティングに追加します。

```ruby:routes.rb
  post 'articles/attach', to: 'articles#attach'
```

## 動作確認

実行して、エディタにうまくドラッグ&ドロップして、画像リンク用のMarkdownが出力されれば成功です。

```bash
rails db:migrate
rails s
```

## 画像のリサイズ

アップロード画像をそのまま返すのではなく、
リサイズしたいものを使用したい場合には、variantで圧縮したものを返すことも可能です。
ActiveStorageは圧縮もやってくれるので楽ですね。

```ruby:app/models/attachment.rb
class Attachment < ApplicationRecord
  has_one_attached :image

  def image_compressed
    if image.attached?
      image.variant(resize_to_fit: [700, 600]).processed
    end
  end
end
```

## まとめ及び所感

Rails6で画像をドラッグ&ドロップ可能なMarkdownエディタを作成する方法を紹介しました。
最近Railsを触りだしたのですが、色々と暗黙のルールを覚えないといけない事はありますが、様々な処理が簡単に実装できるため楽ですね。

## 参考

https://github.com/sparksuite/simplemde-markdown-editor/issues/328