smeditor_rails
Rails integration for the SMEditor rich-text editor.
This gem is a thin adapter. The editor is the upstream npm
packages (@smeditor/react, @smeditor/starter-kit, …); the gem
adds a form helper, a safe server-side renderer, and an optional
upload endpoint. It holds no schema, no commands and no rendering
pipeline of its own.
Requirements
- Ruby >= 3.0, Rails >= 7.0
- A JavaScript bundler in the host app —
jsbundling-rails(esbuild, rollup, webpack, bun) or Vite. The boot script imports the npm packages by bare specifier, which Sprockets and importmap cannot resolve on their own.
Install
# Gemfile
gem "smeditor_rails"
bundle install
bin/rails generate smeditor:install
The generator writes config/initializers/smeditor.rb and copies the
boot script to app/javascript/smeditor.js. Then install the editor
itself and import the script:
npm install @smeditor/react @smeditor/starter-kit \
@smeditor/full-kit @smeditor/theme-default react react-dom
// app/javascript/application.js
import "./smeditor";
Mount the engine if you want the upload endpoint:
# config/routes.rb
mount SMEditor::Rails::Engine => "/smeditor"
Namespace
Константа гема — SMEditor (не Smeditor). Каталоги называются smeditor/,
поэтому движок сам регистрирует переопределение в обоих автозагрузчиках
Zeitwerk (smeditor → SMEditor) до сканирования autoload-путей, а генератор
объявляет namespace явно. Настроек в приложении не требуется, и глобальные
inflections ActiveSupport не трогаются.
Configure
# config/initializers/smeditor.rb
SMEditor.configure do |c|
c.uploads = :active_storage # or :none (default)
c.sanitize_output = true
c.default_kit = "starter" # or "full"
c.upload_path = "/smeditor/uploads"
c. = %w[p h1 h2 h3 strong em a img]
c.max_upload_size = 10 * 1024 * 1024
c.allowed_upload_types = %w[image/png image/jpeg image/webp]
end
Use
The form helper mounts an editor and persists its HTML through a hidden field — a normal form submit saves the content:
<%= form_with model: @article do |form| %>
<%= form.smeditor_editor :content %>
<%= form.submit %>
<% end %>
Options: :kit ("starter" / "full"), :placeholder, :class,
:upload_url.
Display saved content on a public page — always sanitized:
<%= smeditor_render(@article.content) %>
How it works
smeditor_editor renders a hidden field (seeded with the stored
HTML) and an empty mount point. The boot script finds each mount,
attaches a real editor from the npm packages, and writes
editor.getHTML() back into the hidden field on every change. The
model attribute is a plain HTML string — SMEditor's transport format —
so no special column type or serializer is needed.
Booting runs on DOMContentLoaded, on turbo:load and on
turbo:frame-load; each mount is booted at most once.
Rendering and trust
Stored editor HTML is treated as untrusted input. smeditor_render
passes it through SMEditor::Rails::Sanitizer, which applies the tag
allow-list, strips unsafe URLs and style declarations, and adds
rel="noopener noreferrer" to target="_blank" links.
Setting config.sanitize_output = false marks stored content
html_safe without sanitizing it. Only do that when the content is
already trusted upstream.
Uploads
With config.uploads = :active_storage, the engine exposes
POST /smeditor/uploads. It stores image files via ActiveStorage and
responds with { src, alt, title } for the editor to embed.
The endpoint is unauthenticated by default and only enforces the
content-type allow-list and max_upload_size. Subclass
SMEditor::UploadsController and add your own authorization before
exposing it to the public internet.
With :none, no endpoint is mounted — wire your own.
Development
cd gems/smeditor_rails
bundle install
bundle exec rspec
License
MIT © SMEditor contributors