richer_text 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc9681628df0cea14882f90ddc28774653a87d2d7db1ef38049bcbc3e67ddb49
4
- data.tar.gz: e99f8e2774aede03570dd03ec2f995f6b3e8ab46a9c7f4eedcdd7527fde69995
3
+ metadata.gz: 823a38cf937e83676b16a06828bf3a3fa461f8b3d38ae628beecd33dfc3927c2
4
+ data.tar.gz: 4f02bdb13f66fb9b896dee5d52a01806dfab1cf842c4aa33250d1420b49c8fad
5
5
  SHA512:
6
- metadata.gz: 93038afde3fae59acedbdd62d1879cdb7825e2d134d8aa520ae620a66619de0b32a594e5f34114e7dac66566f66523150d689a39500968076317313f15f00267
7
- data.tar.gz: 176ea00a46823251b32ddcb0cd8e9a59eb00eb6f452a2c7c98b43a797bf95e683f4d6df99452ad903ef5c5d6bc2fadf3ff9174f19a31476d07a44455d00b4dd0
6
+ metadata.gz: 1cf13ee1015adee390ccdb1da3a3f2f317f01c70ea2f10364762c526a1619f56aa79085ddae04ab1690ed602bca0ccdedc3f83c0691fca21c9d4f46cc4314ade
7
+ data.tar.gz: 9f1fffb59f348833c3a7ebdf1b86075bddfb2a4b4b4459bb7bdb211418e6fbf34c2ffe42c3b9c70f9c2610f6bd2874386003a11a0aaa75adbc64cc55149f6aa6
@@ -18,6 +18,7 @@
18
18
 
19
19
  .richer-text table td>*,
20
20
  .richer-text table th>* {
21
+ margin-top: 0;
21
22
  margin-bottom: 0;
22
23
  }
23
24
 
@@ -32,5 +33,11 @@
32
33
  }
33
34
 
34
35
  .richer-text img {
35
- width: 100%;
36
+ width: auto;
37
+ }
38
+
39
+ .richer-text blockquote {
40
+ border-left: 2px solid #ced4da;
41
+ margin: 1rem 0.5rem;
42
+ padding: 0 0 0 10px;
36
43
  }
@@ -10,7 +10,8 @@ module ActionView::Helpers
10
10
  options = @options.stringify_keys
11
11
  add_default_name_and_id(options)
12
12
  options["input"] ||= dom_id(object, [options["id"], :richer_text_input].compact.join("_")) if object
13
- options["value"] = options.fetch("value") { value&.to_html }
13
+ options["value"] = options.fetch("value") { value&.to_editor_format }
14
+ options["serializer"] = options.fetch("serializer") { object.class.send(:"richer_text_#{@method_name}_json") ? "json" : "html" }
14
15
 
15
16
  @template_object.richer_text_area_tag(options.delete("name"), options["value"], options.except("value"))
16
17
  end
@@ -40,6 +41,9 @@ module RicherText
40
41
  # So that we can access the content in the tiptap editor
41
42
  options[:content] ||= value
42
43
 
44
+ # So we can choose the serializer to use, e.g. "html" or "json"
45
+ options[:serializer] ||= "html"
46
+
43
47
  input_tag = hidden_field_tag(name, value, id: options[:input])
44
48
  editor_tag = tag("richer-text-editor", options)
45
49
 
@@ -0,0 +1,43 @@
1
+ module RicherText
2
+ class JsonText < ApplicationRecord
3
+ DEFAULT_BODY = {"type" => "doc", "content" => [{"type" => "paragraph", "attrs" => {"textAlign" => "left"}}]}
4
+
5
+ belongs_to :record, polymorphic: true, touch: true
6
+
7
+ serialize :body, JSON, default: DEFAULT_BODY.to_json
8
+
9
+ has_many_attached :images
10
+
11
+ before_save :update_images
12
+
13
+ def to_editor_format
14
+ body
15
+ end
16
+
17
+ def to_s
18
+ RicherText.default_renderer.visit(document)
19
+ end
20
+
21
+ private
22
+
23
+ def update_images
24
+ self.images = image_nodes.map(&:signed_id)
25
+ end
26
+
27
+ def image_nodes
28
+ find_nodes_of_type(RicherText::Nodes::Image).flatten.compact_blank
29
+ end
30
+
31
+ def find_nodes_of_type(type, node = document)
32
+ if node.children.any?
33
+ node.children.map { |child| find_nodes_of_type(type, child) }
34
+ else
35
+ node.is_a?(type) ? node : nil
36
+ end
37
+ end
38
+
39
+ def document
40
+ @document ||= RicherText::Node.build(body)
41
+ end
42
+ end
43
+ end
@@ -15,6 +15,10 @@ module RicherText
15
15
  body&.to_html&.to_s
16
16
  end
17
17
 
18
+ def to_editor_format
19
+ body&.to_html&.to_s
20
+ end
21
+
18
22
  private
19
23
 
20
24
  def update_images
@@ -0,0 +1,13 @@
1
+ class CreateRicherTextJsonTexts < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :richer_text_json_texts do |t|
4
+ t.string :name, null: false
5
+ t.text :body, size: :long
6
+ t.references :record, null: false, polymorphic: true
7
+
8
+ t.timestamps
9
+
10
+ t.index [:record_type, :record_id, :name], name: "index_richer_texts_rich_json_uniqueness", unique: true
11
+ end
12
+ end
13
+ end
@@ -3,7 +3,11 @@ module RicherText
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  class_methods do
6
- def has_richer_text(name)
6
+ def has_richer_text(name, json: false)
7
+ # Store if the attribute is using JSON or not.
8
+ class_attribute :"richer_text_#{name}_json", instance_writer: false
9
+ self.send(:"richer_text_#{name}_json=", json)
10
+
7
11
  class_eval <<-CODE, __FILE__, __LINE__ + 1
8
12
  def #{name}
9
13
  richer_text_#{name} || build_richer_text_#{name}
@@ -19,7 +23,7 @@ module RicherText
19
23
  CODE
20
24
 
21
25
  has_one :"richer_text_#{name}", -> { where(name: name) },
22
- class_name: "RicherText::RichText", as: :record, inverse_of: :record, autosave: true, dependent: :destroy
26
+ class_name: json ? "RicherText::JsonText" : "RicherText::RichText", as: :record, inverse_of: :record, autosave: true, dependent: :destroy
23
27
 
24
28
  scope :"with_richer_text_#{name}", -> { includes("richer_text_#{name}") }
25
29
  end
@@ -0,0 +1,85 @@
1
+ module RicherText
2
+ class HTMLVisitor
3
+ include ActionView::Helpers::TagHelper
4
+
5
+ def visit(node)
6
+ node.accept(self)
7
+ end
8
+
9
+ def visit_blockquote(node)
10
+ "<blockquote>#{visit_children(node).join}</blockquote>"
11
+ end
12
+
13
+ def visit_bullet_list(node)
14
+ "<ul>#{visit_children(node).join}</ul>"
15
+ end
16
+
17
+ def visit_callout(node)
18
+ "<div class='callout' data-color='#{node.color}'>#{visit_children(node).join}</div>"
19
+ end
20
+
21
+ def visit_children(node)
22
+ node.children.map { |child| visit(child) }
23
+ end
24
+
25
+ def visit_code_block(node)
26
+ "<pre><code>#{visit_children(node).join("\n")}</code></pre>"
27
+ end
28
+
29
+ def visit_doc(node)
30
+ "<div class='richer-text'>#{visit_children(node).join("\n")}</div>".html_safe
31
+ end
32
+
33
+ def visit_paragraph(node)
34
+ "<p style='#{node.style}'>#{visit_children(node).join}</p>"
35
+ end
36
+
37
+ def visit_list_item(node)
38
+ "<li>#{visit_children(node).join}</li>"
39
+ end
40
+
41
+ def visit_ordered_list(node)
42
+ "<ol>#{visit_children(node).join}</ol>"
43
+ end
44
+
45
+ def visit_heading(node)
46
+ "<h#{node.level}>#{visit_children(node).join}</h#{node.level}>"
47
+ end
48
+
49
+ def visit_hard_break(_node)
50
+ "<br>"
51
+ end
52
+
53
+ def visit_horizontal_rule(_node)
54
+ "<div><hr></div>"
55
+ end
56
+
57
+ def visit_image(node)
58
+ "<img src='#{node.src}' />"
59
+ end
60
+
61
+ def visit_text(node, marks)
62
+ if marks.empty?
63
+ node.text
64
+ else
65
+ content_tag(marks[0].tag, visit_text(node, marks[1..]), marks[0].attrs)
66
+ end
67
+ end
68
+
69
+ def visit_table(node)
70
+ "<table>#{visit_children(node).join}</table>"
71
+ end
72
+
73
+ def visit_table_row(node)
74
+ "<tr>#{visit_children(node).join}</tr>"
75
+ end
76
+
77
+ def visit_table_cell(node)
78
+ "<td>#{visit_children(node).join}</td>"
79
+ end
80
+
81
+ def visit_table_header(node)
82
+ "<th>#{visit_children(node).join}</th>"
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,23 @@
1
+ module RicherText
2
+ class Mark
3
+ attr_reader :attrs, :type
4
+
5
+ TAGS = {
6
+ "bold" => "strong",
7
+ "italic" => "em",
8
+ "strike" => "del",
9
+ "code" => "code",
10
+ "highlight" => "mark",
11
+ "link" => "a"
12
+ }
13
+
14
+ def initialize(json)
15
+ @attrs = json.fetch("attrs", {})
16
+ @type = json.fetch("type", nil)
17
+ end
18
+
19
+ def tag
20
+ TAGS[type] || "span"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ module RicherText
2
+ class Node
3
+ attr_reader :json, :attrs, :children, :type
4
+
5
+ STYLES = {
6
+ "textAlign" => "text-align",
7
+ }
8
+
9
+ def self.build(json)
10
+ node = json.is_a?(String) ? JSON.parse(json) : json
11
+ klass = "RicherText::Nodes::#{node["type"].underscore.classify}".safe_constantize
12
+ klass.new(node)
13
+ end
14
+
15
+ def initialize(json)
16
+ @json = json
17
+ @attrs = json.fetch("attrs", {})
18
+ @type = json.fetch("type", "text")
19
+ @children = json.fetch("content", []).map { |child| self.class.build(child) }
20
+ end
21
+
22
+ def style
23
+ @attrs.select { |k, v| STYLES.key?(k) }.map { |k, v| "#{STYLES[k]}: #{v};" }.join
24
+ end
25
+
26
+ def accept(visitor)
27
+ visitor.send("visit_#{type.underscore}", self)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,6 @@
1
+ module RicherText
2
+ module Nodes
3
+ class Blockquote < ::RicherText::Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RicherText
2
+ module Nodes
3
+ class BulletList < ::RicherText::Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module RicherText
2
+ module Nodes
3
+ class Callout < ::RicherText::Node
4
+ def color
5
+ attrs["data-color"]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module RicherText
2
+ module Nodes
3
+ class CodeBlock < ::RicherText::Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RicherText
2
+ module Nodes
3
+ class Doc < ::RicherText::Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RicherText
2
+ module Nodes
3
+ class HardBreak < ::RicherText::Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module RicherText
2
+ module Nodes
3
+ class Heading < ::RicherText::Node
4
+ def level
5
+ attrs["level"]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module RicherText
2
+ module Nodes
3
+ class HorizontalRule < ::RicherText::Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ module RicherText
2
+ module Nodes
3
+ class Image < ::RicherText::Node
4
+ def src
5
+ @src ||= attrs['src']
6
+ end
7
+
8
+ def signed_id
9
+ @signed_id = attrs['signedId']
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ module RicherText
2
+ module Nodes
3
+ class ListItem < ::RicherText::Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RicherText
2
+ module Nodes
3
+ class OrderedList < ::RicherText::Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RicherText
2
+ module Nodes
3
+ class Paragraph < ::RicherText::Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RicherText
2
+ module Nodes
3
+ class Table < ::RicherText::Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RicherText
2
+ module Nodes
3
+ class TableCell < ::RicherText::Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RicherText
2
+ module Nodes
3
+ class TableHeader < ::RicherText::Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RicherText
2
+ module Nodes
3
+ class TableRow < ::RicherText::Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ module RicherText
2
+ module Nodes
3
+ class Text < ::RicherText::Node
4
+ def initialize(json)
5
+ @marks = json.fetch("marks", []).map { |mark| RicherText::Mark.new(mark) }
6
+ super(json)
7
+ end
8
+
9
+ def text
10
+ json["text"]
11
+ end
12
+
13
+ def accept(visitor)
14
+ visitor.visit_text(self, @marks)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module RicherText
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/richer_text.rb CHANGED
@@ -13,4 +13,38 @@ module RicherText
13
13
  autoload :Fragment
14
14
  autoload :Serialization
15
15
  autoload :TagHelper
16
+
17
+ autoload :Node
18
+ autoload :Mark
19
+ autoload :HTMLVisitor
20
+
21
+ module Nodes
22
+ extend ActiveSupport::Autoload
23
+
24
+ autoload :Blockquote
25
+ autoload :BulletList
26
+ autoload :Callout
27
+ autoload :CodeBlock
28
+ autoload :Doc
29
+ autoload :HardBreak
30
+ autoload :Heading
31
+ autoload :HorizontalRule
32
+ autoload :Image
33
+ autoload :ListItem
34
+ autoload :Paragraph
35
+ autoload :OrderedList
36
+ autoload :Text
37
+ autoload :Table
38
+ autoload :TableCell
39
+ autoload :TableRow
40
+ autoload :TableHeader
41
+ end
42
+
43
+ class << self
44
+ def default_renderer
45
+ @default_renderer ||= RicherText::HTMLVisitor.new
46
+ end
47
+
48
+ attr_writer :default_renderer
49
+ end
16
50
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: richer_text
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrea Fomera
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-02 00:00:00.000000000 Z
11
+ date: 2023-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -43,17 +43,39 @@ files:
43
43
  - app/jobs/richer_text/application_job.rb
44
44
  - app/mailers/richer_text/application_mailer.rb
45
45
  - app/models/richer_text/application_record.rb
46
+ - app/models/richer_text/json_text.rb
46
47
  - app/models/richer_text/rich_text.rb
47
48
  - app/views/layouts/richer_text/application.html.erb
48
49
  - app/views/richer_text/contents/_content.html.erb
49
50
  - config/routes.rb
50
51
  - db/migrate/20230107020316_create_richer_text_rich_texts.rb
52
+ - db/migrate/20230916224959_create_richer_text_json_texts.rb
51
53
  - lib/generators/richer_text/install/install_generator.rb
52
54
  - lib/richer_text.rb
53
55
  - lib/richer_text/attribute.rb
54
56
  - lib/richer_text/content.rb
55
57
  - lib/richer_text/engine.rb
56
58
  - lib/richer_text/fragment.rb
59
+ - lib/richer_text/html_visitor.rb
60
+ - lib/richer_text/mark.rb
61
+ - lib/richer_text/node.rb
62
+ - lib/richer_text/nodes/blockquote.rb
63
+ - lib/richer_text/nodes/bullet_list.rb
64
+ - lib/richer_text/nodes/callout.rb
65
+ - lib/richer_text/nodes/code_block.rb
66
+ - lib/richer_text/nodes/doc.rb
67
+ - lib/richer_text/nodes/hard_break.rb
68
+ - lib/richer_text/nodes/heading.rb
69
+ - lib/richer_text/nodes/horizontal_rule.rb
70
+ - lib/richer_text/nodes/image.rb
71
+ - lib/richer_text/nodes/list_item.rb
72
+ - lib/richer_text/nodes/ordered_list.rb
73
+ - lib/richer_text/nodes/paragraph.rb
74
+ - lib/richer_text/nodes/table.rb
75
+ - lib/richer_text/nodes/table_cell.rb
76
+ - lib/richer_text/nodes/table_header.rb
77
+ - lib/richer_text/nodes/table_row.rb
78
+ - lib/richer_text/nodes/text.rb
57
79
  - lib/richer_text/rendering.rb
58
80
  - lib/richer_text/serialization.rb
59
81
  - lib/richer_text/version.rb