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 +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
|