はじめに
Rails 6でAction Textという機能が追加されました。Action Textを使えば簡単にリッチテキストエディターを実装することができます。Action TextはTrixエディターを使用しています。Trixエディターは「WYSIWYG」と呼ばれる編集方式を採用しているエディターです。
WYSIWYGとは、見たままのものを実際に作成出力するという言葉のWhat You See Is What You Getの頭文字をとったものであり、WYSIWYGエディターとは編集中の画面に表示されるものと同じものが、最終結果(HTML、印刷結果等)として得られるようなアプリケーションのこと。CMS用語集
例えば、文字を大きくしたいならエディター内の文字を直接大きくしたり、画像を挿入したいならエディター内に画像を直接挿入します。エディター内に表示されているものがそのまま生成されるのが特徴です。
この画像はAction Textの編集画面の例です。エディター内のテキストが大きくなったり太字になっているのがわかります。
Action Textの実装
Railsアプリの作成
以下のコマンドを実行して、新しいRailsアプリを作成します。
$ rails new rails-actiontext
以下のコマンドを実行して、基本的なブログの実装に必要なコントローラーやモデルなどを一括作成します。
$ rails g scaffold Article title:string
$ rails db:migrate
この時点でテストサーバーを起動して確認してみます。
$ rails s
テストサーバーが起動したらhttp://localhost:3000/article
にアクセスし、以下のようにタイトルのみのビューが表示されていればOKです。
Action Textの作成
以下のコマンドを実行して、Action Textをインストールします。
$ rails action_text:install
上記コマンドを実行すると、Action Textで入力されたリッチテキストや挿入された画像を保存するテーブルを作成するためのマイグレーションが作成されます。以下のコマンドを実行して、これらのテーブルをデータベーススキーマに追加します。
$ rails db:migrate
記事のモデルarticle.rb
に以下を追記します。本文となるAction Textを追加しています。
article.rb
class Article < ApplicationRecord
# 以下を追記
has_rich_text :content
end
記事のコントローラーarticles_controller.rb
に以下を追記します。コントローラーのストロングパラメーターに本文を追加しています。
articles_controller.rb
def article_params
params.require(:article).permit(:title, :content)
end
記事の投稿/編集フォーム_form.html.erb
または_form.html.slim
に以下を追記します。フォームに本文となるAction Textを追加しています。
_form.html.erb
<%= form_with(model: article, local: true) do |form| %>
<div class="field">
<%= form.label :title %>
<%= form.text_field :title %>
<%# 以下を追記 %>
<%= form.label :content %>
<%= form.rich_text_area :content %>
</div>
<% end %>
_form.html.slim
= form_with(model: article, local: true) do |form|
.field
= form.label :title
= form.text_field :title
/ 以下を追記
= form.label :content
= form.rich_text_area :content
記事の詳細ページshow.html.erb
またはshow.html.slim
に以下を追記します。記事の詳細ページにAction Textで入力した本文を追加しています。
show.html.erb
<%# 以下を追記 %>
<p>
<strong>Content:</strong>
<%= @article.content %>
</p>
show.html.slim
/ 以下を追記
p
strong Content:
= @article.content
これでAction Textの実装ができました。http://localhost:3000/article/new
にアクセスし、以下のように本文が追加されていればOKです。
記事の詳細ページは以下の通りです。編集中に見ていた通りの内容が表示されています。
埋め込み画像の保存先
Action Textに挿入した画像の保存先はActive Storageによって決定されます。Action Textで画像を扱う場合はActive Storageのセットアップを行う必要があります。
テーブル(表)の挿入
Action Textが使用しているTrixエディターはテーブル(表)をサポートしていません。記事でテーブル(表)を挿入したい場合はMarkdownの使用をおすすめしますが、どうしてもAction Textでテーブル(表)を扱いたい場合は以下の手順を行います。
lib/
ディレクトリ配下にrichtext/code_blocks/
ディレクトリを作成し、その中にhtml_service.rb
を作成します。作成したファイルに以下を記述します。
html_service.rb
class Richtext::CodeBlocks::HtmlService
ALLOWED_HTML_TAGS = ["table", "tr", "td", "th", "col", "pre", "p", "h1", "h2", "h3", "summary", "details", "row", "code"]
ALLOWED_HTML_ATTRIBUTES = []
# To Validate HTML tags and protect from bad formatted input
def self.validate(html)
errors = []
html = Nokogiri::HTML::DocumentFragment.parse(html)
html.search("pre").each do |pre_tag|
pre_tag_html, pre_tag_errors = self.ensure_well_formed_markup(pre_tag.text)
errors.push(pre_tag_errors) unless pre_tag_errors.empty?
inner_html = self.extract_inner_html_from_pre_tag(pre_tag_html)
inner_html = self.remove_not_allowed_tags_and_attributes(inner_html)
pre_tag.children.first.replace(Nokogiri::XML::Text.new(
inner_html,
pre_tag
))
end
html = ActionText::Fragment.new(html)
[html.to_html, errors.flatten.uniq]
end
# To parse each code block tag and render it to HTML
def self.render(rich_text)
html = Nokogiri::HTML::DocumentFragment.parse(rich_text)
html.search("pre").each do |pre_tag|
inner_html = Nokogiri::HTML::DocumentFragment.parse(pre_tag.text)
inner_html = add_styles_to_tables(inner_html)
advanced_code_block = "<div class='advanced-code-block'>#{inner_html.to_html}</div>"
pre_tag.replace(advanced_code_block)
end
html.to_html
end
private
def self.ensure_well_formed_markup(html)
parsed = Nokogiri::XML("<pre>#{html}</pre>")
[parsed, parsed.errors]
end
# To add our bootstrap specific classes to table elements
def self.add_styles_to_tables(html)
html.search("table").each do |table|
table["class"] = "table"
table.wrap("<div class='table-responsive'></div>")
end
html
end
def self.extract_inner_html_from_pre_tag(html)
Nokogiri::XML(html.at("pre").inner_html)
end
# To add error messages to mis-formatted HTML
def self.error_messages(errors)
readable_message = ->(e) { e.message.split(":")[3].strip rescue "" }
errors.map {|e| readable_message.call(e) }.uniq.join(", ")
end
end
config/application.rb
に以下を追記します。作成したクラスをRailsアプリの起動時にロードしています。
application.rb
module ApplicationName
class Application < Rails::Application
# 以下を追記
config.autoload_paths += %W(#{config.root}/lib #{config.root}/lib)
end
end
モデルarticle.rb
に以下を追記します。
article.rb
class Article < ApplicationRecord
# 以下を追記
def formatted_content
Richtext::CodeBlocks::HtmlService.render(self.content.to_s).html_safe
end
end
記事の詳細ページshow.html.erb
またはshow.html.slim
を以下のように修正します。
show.html.erb
<p>
<strong>Content:</strong>
<%# 以下を修正 %>
<%= @article.formatted_content %>
</p>
show.html.slim
p
strong Content:
/ 以下を修正
= @article.formatted_content
テーブル(表)を含んだ記事を投稿するには以下のように入力します。コードブロックの中にHTMLでテーブルタグを記述します。
この記事を投稿すると以下のように表示されます。
上記の例ではわかりやすいようにテーブルのスタイルを設定しています。この方法では、コードブロックの中に記述したテーブルタグはHTMLとして解釈されレンダリングされます。
この方法のデメリットは以下の通りです。これらのデメリットをよく理解した上で導入を検討してください。
- コードブロックが本来の用途で使えなくなる。
- 記事の投稿者はテーブルタグの仕様を理解している必要がある。
- テーブル(表)の編集時はWYSIWYGとしての特性を活かせない。
まとめ
Action Textを導入することで簡単にリッチテキストエディターを実装することができます。記事をリッチテキストエディターではなくMarkdownで書きたい場合はAction Textではなくマークダウンエディターを実装します。
本記事を参考にして、Action Textの使い方を覚えていただければと思います。