Class: Domternal::RichText
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- Domternal::RichText
- Defined in:
- app/models/domternal/rich_text.rb
Overview
Holds the HTML body + Active Storage embeds for a single has_rich_domternal attribute.
Body is stored as the same sanitized HTML the Domternal editor renders/re-parses
(Domternal's Editor/DomternalEditor accept HTML directly as content), so no
separate JSON<->HTML conversion step is needed on the Ruby side.
Constant Summary collapse
- PARAGRAPH_TAGS =
How #to_plain_text separates elements: paragraph-level blocks get a blank line between them, list items and table rows a single newline, and table cells a space.
%w[ p div h1 h2 h3 h4 h5 h6 blockquote pre figure figcaption ul ol table details ].freeze
- LINE_TAGS =
%w[li tr summary].freeze
- CELL_TAGS =
%w[td th].freeze
- BREAK =
Private-use codepoint: not whitespace, so the collapse in #to_plain_text leaves it alone, and Nokogiri accepts it in a text node (unlike NUL).
"\uE000"
Instance Method Summary collapse
-
#to_plain_text ⇒ Object
Block-aware, like ActionText's plain text conversion: block boundaries and
become newlines rather than being collapsed into spaces. - #to_s ⇒ Object
Instance Method Details
#to_plain_text ⇒ Object
Block-aware, like ActionText's plain text conversion: block boundaries and
become
newlines rather than being collapsed into spaces. Callers rely on this — line-oriented
content (one list item or answer per line) has to survive the round trip, and search
index columns built from rich text read far better with the structure kept.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'app/models/domternal/rich_text.rb', line 38 def to_plain_text html = body.to_s return "" if html.blank? fragment = Nokogiri::HTML5.fragment(html) # Breaks are marked with a sentinel rather than a real newline so that the whitespace # collapse below can't tell them apart from incidental newlines in the source markup. fragment.css("br").each { |node| node.replace(text_node(node, BREAK)) } fragment.css(*CELL_TAGS).each { |node| node.add_next_sibling(text_node(node, " ")) } fragment.css(*LINE_TAGS).each { |node| node.add_next_sibling(text_node(node, BREAK)) } fragment.css(*PARAGRAPH_TAGS).each { |node| node.add_next_sibling(text_node(node, BREAK * 2)) } fragment.text .gsub(/[^\S\n]+/, " ") # spaces and tabs collapse; newlines in text are content .gsub(/ ?(#{BREAK}(?: ?#{BREAK})*) ?/o) { "\n" * Regexp.last_match(1).count(BREAK) } .gsub(/ *\n */, "\n") .gsub(/\n{3,}/, "\n\n") .strip end |
#to_s ⇒ Object
30 31 32 |
# File 'app/models/domternal/rich_text.rb', line 30 def to_s body.to_s.html_safe # rubocop:disable Rails/OutputSafety -- sanitized in #sanitize_body before persistence end |