action_text_cr
Version, currently 0.1.02 versions
- 0.1.0latestOct 12, 2026
- master branchOct 12, 2026
github.com/OrelSokolov/action_text.cr
No description declared in shard.yml.
Installation
# Add this to your shard.yml
dependencies:
action_text_cr:
github: OrelSokolov/action_text.cr
version: ~> 0.1.0Then run:
shards installshard.yml
- Crystal
>= 1.21.0
Dependencies
Runtime Dependencies
- lexbor*github: kostya/lexbor
- active_record_cr~> 0.1.0github: OrelSokolov/active_record.cr
README
action_text.cr
A 1:1 Crystal port of actiontext 8.1.4 (the Rails rich-text editing
component, namespace ActionText). Implements the has_rich_text
attribute macro with autosave, the action_text_rich_texts store, and
the ActionText::Content pipeline: HTML fragment
parsing/serialization, plain-text conversion, safe-list sanitization
(safe list taken verbatim from rails-html-sanitizer 1.7.1 plus the
action-text attachment tags) and the trix-content layout render —
byte-identical to the gem. Suite: 48/48 oracle cases recorded from
actiontext 8.1.4 and replayed without edits.
⚠️ EXPERIMENTAL FRAMEWORK, DO NOT USE IN PRODUCTION YET.
Installation
dependencies:
action_text_cr:
github: OrelSokolov/action_text.cr
shards install
Depends on active_record_cr and the lexbor shard.
Usage
require "action_text_cr"
class Message < ActiveRecord::Base
has_rich_text :body
end
message = Message.create!(body: "<h1>Hello</h1><div>World<a href='x'>l</a></div>")
message.body.to_s # "<div class=\"trix-content\">\n ...\n</div>\n"
message.body.to_plain_text # block/br/quote/list rules, chomp("") semantics
message.body.to_body_content.links # ["x"]
message.body? # true (non-blank plain text)
Feature comparison vs actiontext 8.1.4
Rich text attribute & persistence
| Feature | Original (actiontext) | Port (action_text.cr) | Status |
|---|---|---|---|
has_rich_text :name — getter / setter / presence | ✅ | Macro generating body / body= / body? | ✅ |
| Autosave of the RichText row with its owner | ✅ | after_create / after_update callbacks | ✅ |
create! / create accepting the rich-text key in the attribute hash | ✅ | Key pulled out before AR create runs | ✅ |
action_text_rich_texts store (name/body/record_type/record_id) | ✅ | RichText row class over Base.connection | ⚠️ partial (hand-rolled, not a full AR model) |
with_rich_text_* scope / eager loading | ✅ | ❌ | ❌ |
Content pipeline & rendering
| Feature | Original | Port | Status |
|---|---|---|---|
Content#to_s — trix-content layout render | ✅ | Static layout: <div class="trix-content">\n …\n</div>\n | ✅ |
Content#to_plain_text | ✅ | Bottom-up rules 1:1 (block/br/div/figcaption/blockquote quotes/ul-ol-li with indentation, chomp("")) | ✅ |
Content#to_html, #links | ✅ | Same (lexbor serialization 1:1 with Nokogiri HTML5) | ✅ |
Serialization (Content.load / .dump) | ✅ | Same | ✅ |
| Sanitization (Rails::HTML4 safe-list + attachment tags, unwrap-keep-content) | ✅ | Safe list verbatim from rails-html-sanitizer 1.7.1 | ✅ |
Content#to_markdown | ✅ | ❌ | ❌ (P2) |
RichText#to_editor_html / Trix conversion | ✅ | ❌ | ❌ (P1) |
Attachments & views
| Feature | Original | Port | Status |
|---|---|---|---|
<action-text-attachment sgid=…> resolution (sgid → allowed classes) | ✅ | ❌ | ❌ (P1) |
| ActiveStorage blob attachments in content, attachment galleries | ✅ | ❌ | ❌ (P1/P2) |
Custom render partial app/views/action_text/contents/_content | ✅ | ❌ | ❌ (P1) |
content.html direct render | ✅ | ❌ | ❌ |
encrypted: true rich text | ✅ | ❌ | ❌ (P2) |
| Trix editor | client-side JS | Not ported — browser JS works as-is | ✅ (n/a) |
Deviations & Crystal-specific notes
RichTextis a hand-rolled row class onActiveRecord::Base.connection, not a full ActiveRecord model — the AR codegen would override#to_s/#bodywith attribute accessors (later defs win in Crystal).RichText#bodyis the raw HTML string exactly as stored in the DB; get theContentobject viato_body_content.- HTML fragment transformations run on lexbor instead of Nokogiri; serialization output is 1:1 with the Nokogiri HTML5 oracle.
- The sanitizer reproduces the libxml2
\nartifact only for the unwrap of a trailing script/style child (the oracle cases cover this).
Full list with rationale: PLAN.md (Russian).
Testing
make spec # crystal spec --link-flags="-lm"; lexbor needs libm at link time
Requires a local PostgreSQL (override with AT_TEST_URL). Oracle:
48 cases recorded with the real actiontext 8.1.4
(oracle/cases.rb → oracle/expected.yml, SQLite in-memory on the
Ruby side); the Crystal spec spec/oracle_spec.cr replays them 1:1
without edits — 48/48.
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
0.1.0- Tagged
- Oct 12, 2026
- Commit
5d7b43a8f4ec- Crystal
>= 1.21.0- Indexed
- yes
Dependents
No indexed shard depends on this one yet.
Repository
github.com/OrelSokolov/action_text.cr
Metadata
- Created
- Oct 13, 2026
- Updated
- Oct 24, 2026
- Synced
- Oct 24, 2026
- Versions
- 2