richer_text 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/richer_text/richer-text.css +8 -1
- data/app/helpers/richer_text/tag_helper.rb +5 -1
- data/app/models/richer_text/json_text.rb +43 -0
- data/app/models/richer_text/rich_text.rb +4 -0
- data/db/migrate/20230916224959_create_richer_text_json_texts.rb +13 -0
- data/lib/richer_text/attribute.rb +6 -2
- data/lib/richer_text/html_visitor.rb +85 -0
- data/lib/richer_text/mark.rb +23 -0
- data/lib/richer_text/node.rb +30 -0
- data/lib/richer_text/nodes/blockquote.rb +6 -0
- data/lib/richer_text/nodes/bullet_list.rb +6 -0
- data/lib/richer_text/nodes/callout.rb +9 -0
- data/lib/richer_text/nodes/code_block.rb +6 -0
- data/lib/richer_text/nodes/doc.rb +6 -0
- data/lib/richer_text/nodes/hard_break.rb +6 -0
- data/lib/richer_text/nodes/heading.rb +9 -0
- data/lib/richer_text/nodes/horizontal_rule.rb +6 -0
- data/lib/richer_text/nodes/image.rb +13 -0
- data/lib/richer_text/nodes/list_item.rb +6 -0
- data/lib/richer_text/nodes/ordered_list.rb +6 -0
- data/lib/richer_text/nodes/paragraph.rb +6 -0
- data/lib/richer_text/nodes/table.rb +6 -0
- data/lib/richer_text/nodes/table_cell.rb +6 -0
- data/lib/richer_text/nodes/table_header.rb +6 -0
- data/lib/richer_text/nodes/table_row.rb +6 -0
- data/lib/richer_text/nodes/text.rb +18 -0
- data/lib/richer_text/version.rb +1 -1
- data/lib/richer_text.rb +34 -0
- metadata +24 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 823a38cf937e83676b16a06828bf3a3fa461f8b3d38ae628beecd33dfc3927c2
|
4
|
+
data.tar.gz: 4f02bdb13f66fb9b896dee5d52a01806dfab1cf842c4aa33250d1420b49c8fad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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:
|
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&.
|
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
|
@@ -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,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
|
data/lib/richer_text/version.rb
CHANGED
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.
|
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-
|
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
|