ActionText: ブロックをエディタ要素のDOM子要素として描画するよう変更
ActionTextのブロック引数が持つセマンティクスが変更され、ブロックはエディタ要素のDOM子要素として描画されるようになりました。これにより、value(リッチテキストの内容)とブロック(カスタム要素の埋め込み)が独立して扱えるようになります。
背景
#55827 でActionTextのタグヘルパーにブロック引数のサポートが導入されましたが、その実装はブロックを value の代替として扱うものでした。つまり value が未指定のときにブロックの内容を初期テキストとして使用する「value OR block」の設計であり、両者を同時に利用することはできませんでした。
この制約は、LexxyのようなActionText互換のエディタとの統合において問題になります。Lexxyでは、ブロックをリッチテキストの初期値ではなく、エディタ要素へのカスタム子要素(プロンプトメニュー等)の注入に使用します。この変更は、Lexxyのような他のエディタとの統合を公式にサポートするものです。
技術的な変更
ブロックの扱いがヘルパー層からエディタタグ層へ移動し、value との独立性が確保されました。
rich_textarea_tag の変更(tag_helper.rb):
変更前はヘルパー側でブロックを早期にキャプチャし、value に代入していました。
変更前:
def rich_textarea_tag(name, value = nil, options = {}, &block)
value = capture(&block) if value.nil? && block_given?
options = options.symbolize_keys
# ...
render RichText.editor.editor_tag(options)
end
変更後:
def rich_textarea_tag(name, value = nil, options = {}, &block)
options = options.symbolize_keys
# ...
render RichText.editor.editor_tag(options, &block)
end
ブロックのキャプチャが削除され、ブロック自体が editor_tag へそのまま渡されます。
Editor::Tag の変更(editor.rb):
Editor::Tag がブロックを保持し、render_in で content_tag に渡すことでDOM子要素として展開します。
class Editor::Tag
def initialize(editor_name, options = {}, &block)
@editor_name = editor_name
@options = options
@block = block
end
def render_in(view_context)
options[:class] ||= "#{editor_name}-content"
view_context.content_tag(element_name, nil, options, &@block)
end
end
Trixの後方互換性(trix_editor.rb):
TrixEditor::Tag#render_in には特別な処理が追加されています。Trixは隠しinputで値を管理するため、value が未指定かつブロックが存在する場合に限り、ブロックをキャプチャして隠しinputの値として使用します。これにより「ブロックを初期値として使う」という既存の動作が維持されます。
def render_in(view_context, ...)
name = options.delete(:name)
form = options.delete(:form)
value = options.delete(:value)
value = view_context.capture(&@block) if @block && value.nil?
options[:class] ||= "#{editor_name}-content"
options[:input] ||= # ...
input_tag = view_context.hidden_field_tag(name, value, id: options[:input], form: form)
input_tag + view_context.content_tag(element_name, nil, options)
end
注意点として、Trixは super(親の render_in)を呼ぶのをやめ、content_tag を直接呼び出すようになっています。これはブロックをTrixの子要素として描画させないためです。Trixにおいてブロックは value のフォールバックとしてのみ機能し、DOM子要素にはなりません。
設計判断
エディタの種類によってブロックのセマンティクスを使い分ける設計が採用されました。
カスタムエディタ(Editor::Tag)ではブロックがDOM子要素として展開されます。一方Trixは value が存在しない場合のみブロックを初期値として解釈し、value がある場合は子要素への展開も行いません。この非対称な動作は意図的なもので、Trixの動作における後方互換性を保ちながら、新しいエディタに対しては柔軟な子要素注入の手段を提供します。
この設計では value とブロックの関係がエディタの実装クラスに委ねられており、将来の新しいエディタが独自のセマンティクスを定義できる拡張性を持ちます。
まとめ
ブロックの役割をDOM子要素の注入へと再定義することで、value(リッチテキスト内容)とブロック(UI拡張要素)が独立した関心事として扱えるようになりました。Trixの後方互換性を保ちつつ、Lexxyのようなアーキテクチャを公式にサポートする設計拡張点として機能します。