actiontext 6.0.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actiontext might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +40 -0
- data/MIT-LICENSE +21 -0
- data/README.md +9 -0
- data/app/helpers/action_text/content_helper.rb +37 -0
- data/app/helpers/action_text/tag_helper.rb +79 -0
- data/app/javascript/actiontext/attachment_upload.js +45 -0
- data/app/javascript/actiontext/index.js +10 -0
- data/app/models/action_text/rich_text.rb +29 -0
- data/app/views/action_text/attachables/_missing_attachable.html.erb +1 -0
- data/app/views/action_text/attachables/_remote_image.html.erb +8 -0
- data/app/views/action_text/attachment_galleries/_attachment_gallery.html.erb +3 -0
- data/app/views/action_text/content/_layout.html.erb +3 -0
- data/app/views/active_storage/blobs/_blob.html.erb +14 -0
- data/db/migrate/20180528164100_create_action_text_tables.rb +13 -0
- data/lib/action_text.rb +37 -0
- data/lib/action_text/attachable.rb +86 -0
- data/lib/action_text/attachables/content_attachment.rb +38 -0
- data/lib/action_text/attachables/missing_attachable.rb +13 -0
- data/lib/action_text/attachables/remote_image.rb +46 -0
- data/lib/action_text/attachment.rb +103 -0
- data/lib/action_text/attachment_gallery.rb +65 -0
- data/lib/action_text/attachments/caching.rb +16 -0
- data/lib/action_text/attachments/minification.rb +17 -0
- data/lib/action_text/attachments/trix_conversion.rb +34 -0
- data/lib/action_text/attribute.rb +45 -0
- data/lib/action_text/content.rb +132 -0
- data/lib/action_text/engine.rb +54 -0
- data/lib/action_text/fragment.rb +57 -0
- data/lib/action_text/gem_version.rb +17 -0
- data/lib/action_text/html_conversion.rb +24 -0
- data/lib/action_text/plain_text_conversion.rb +81 -0
- data/lib/action_text/serialization.rb +34 -0
- data/lib/action_text/trix_attachment.rb +92 -0
- data/lib/action_text/version.rb +10 -0
- data/lib/tasks/actiontext.rake +20 -0
- data/lib/templates/actiontext.scss +36 -0
- data/lib/templates/fixtures.yml +4 -0
- data/lib/templates/installer.rb +45 -0
- data/package.json +29 -0
- metadata +161 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails"
|
4
|
+
require "action_controller/railtie"
|
5
|
+
require "active_record/railtie"
|
6
|
+
require "active_storage/engine"
|
7
|
+
|
8
|
+
require "action_text"
|
9
|
+
|
10
|
+
module ActionText
|
11
|
+
class Engine < Rails::Engine
|
12
|
+
isolate_namespace ActionText
|
13
|
+
config.eager_load_namespaces << ActionText
|
14
|
+
|
15
|
+
initializer "action_text.attribute" do
|
16
|
+
ActiveSupport.on_load(:active_record) do
|
17
|
+
include ActionText::Attribute
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
initializer "action_text.attachable" do
|
22
|
+
ActiveSupport.on_load(:active_storage_blob) do
|
23
|
+
include ActionText::Attachable
|
24
|
+
|
25
|
+
def previewable_attachable?
|
26
|
+
representable?
|
27
|
+
end
|
28
|
+
|
29
|
+
def attachable_plain_text_representation(caption = nil)
|
30
|
+
"[#{caption || filename}]"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
initializer "action_text.helper" do
|
36
|
+
ActiveSupport.on_load(:action_controller_base) do
|
37
|
+
helper ActionText::Engine.helpers
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
initializer "action_text.renderer" do |app|
|
42
|
+
app.executor.to_run { ActionText::Content.renderer = ApplicationController.renderer }
|
43
|
+
app.executor.to_complete { ActionText::Content.renderer = ApplicationController.renderer }
|
44
|
+
|
45
|
+
ActiveSupport.on_load(:action_text_content) do
|
46
|
+
self.renderer = ApplicationController.renderer
|
47
|
+
end
|
48
|
+
|
49
|
+
ActiveSupport.on_load(:action_controller_base) do
|
50
|
+
before_action { ActionText::Content.renderer = ApplicationController.renderer.new(request.env) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionText
|
4
|
+
class Fragment
|
5
|
+
class << self
|
6
|
+
def wrap(fragment_or_html)
|
7
|
+
case fragment_or_html
|
8
|
+
when self
|
9
|
+
fragment_or_html
|
10
|
+
when Nokogiri::HTML::DocumentFragment
|
11
|
+
new(fragment_or_html)
|
12
|
+
else
|
13
|
+
from_html(fragment_or_html)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def from_html(html)
|
18
|
+
new(ActionText::HtmlConversion.fragment_for_html(html.to_s.strip))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :source
|
23
|
+
|
24
|
+
def initialize(source)
|
25
|
+
@source = source
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_all(selector)
|
29
|
+
source.css(selector)
|
30
|
+
end
|
31
|
+
|
32
|
+
def update
|
33
|
+
yield source = self.source.clone
|
34
|
+
self.class.new(source)
|
35
|
+
end
|
36
|
+
|
37
|
+
def replace(selector)
|
38
|
+
update do |source|
|
39
|
+
source.css(selector).each do |node|
|
40
|
+
node.replace(yield(node).to_s)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_plain_text
|
46
|
+
@plain_text ||= PlainTextConversion.node_to_plain_text(source)
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_html
|
50
|
+
@html ||= HtmlConversion.node_to_html(source)
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
to_html
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionText
|
4
|
+
# Returns the currently-loaded version of Action Text as a <tt>Gem::Version</tt>.
|
5
|
+
def self.gem_version
|
6
|
+
Gem::Version.new VERSION::STRING
|
7
|
+
end
|
8
|
+
|
9
|
+
module VERSION
|
10
|
+
MAJOR = 6
|
11
|
+
MINOR = 0
|
12
|
+
TINY = 2
|
13
|
+
PRE = nil
|
14
|
+
|
15
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionText
|
4
|
+
module HtmlConversion
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def node_to_html(node)
|
8
|
+
node.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_HTML)
|
9
|
+
end
|
10
|
+
|
11
|
+
def fragment_for_html(html)
|
12
|
+
document.fragment(html)
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_element(tag_name, attributes = {})
|
16
|
+
document.create_element(tag_name, attributes)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def document
|
21
|
+
Nokogiri::HTML::Document.new.tap { |doc| doc.encoding = "UTF-8" }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionText
|
4
|
+
module PlainTextConversion
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def node_to_plain_text(node)
|
8
|
+
remove_trailing_newlines(plain_text_for_node(node))
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def plain_text_for_node(node, index = 0)
|
13
|
+
if respond_to?(plain_text_method_for_node(node), true)
|
14
|
+
send(plain_text_method_for_node(node), node, index)
|
15
|
+
else
|
16
|
+
plain_text_for_node_children(node)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def plain_text_for_node_children(node)
|
21
|
+
node.children.each_with_index.map do |child, index|
|
22
|
+
plain_text_for_node(child, index)
|
23
|
+
end.compact.join("")
|
24
|
+
end
|
25
|
+
|
26
|
+
def plain_text_method_for_node(node)
|
27
|
+
:"plain_text_for_#{node.name}_node"
|
28
|
+
end
|
29
|
+
|
30
|
+
def plain_text_for_block(node, index = 0)
|
31
|
+
"#{remove_trailing_newlines(plain_text_for_node_children(node))}\n\n"
|
32
|
+
end
|
33
|
+
|
34
|
+
%i[ h1 p ul ol ].each do |element|
|
35
|
+
alias_method :"plain_text_for_#{element}_node", :plain_text_for_block
|
36
|
+
end
|
37
|
+
|
38
|
+
def plain_text_for_br_node(node, index)
|
39
|
+
"\n"
|
40
|
+
end
|
41
|
+
|
42
|
+
def plain_text_for_text_node(node, index)
|
43
|
+
remove_trailing_newlines(node.text)
|
44
|
+
end
|
45
|
+
|
46
|
+
def plain_text_for_div_node(node, index)
|
47
|
+
"#{remove_trailing_newlines(plain_text_for_node_children(node))}\n"
|
48
|
+
end
|
49
|
+
|
50
|
+
def plain_text_for_figcaption_node(node, index)
|
51
|
+
"[#{remove_trailing_newlines(plain_text_for_node_children(node))}]"
|
52
|
+
end
|
53
|
+
|
54
|
+
def plain_text_for_blockquote_node(node, index)
|
55
|
+
text = plain_text_for_block(node)
|
56
|
+
text.sub(/\A(\s*)(.+?)(\s*)\Z/m, '\1“\2”\3')
|
57
|
+
end
|
58
|
+
|
59
|
+
def plain_text_for_li_node(node, index)
|
60
|
+
bullet = bullet_for_li_node(node, index)
|
61
|
+
text = remove_trailing_newlines(plain_text_for_node_children(node))
|
62
|
+
"#{bullet} #{text}\n"
|
63
|
+
end
|
64
|
+
|
65
|
+
def remove_trailing_newlines(text)
|
66
|
+
text.chomp("")
|
67
|
+
end
|
68
|
+
|
69
|
+
def bullet_for_li_node(node, index)
|
70
|
+
if list_node_name_for_li_node(node) == "ol"
|
71
|
+
"#{index + 1}."
|
72
|
+
else
|
73
|
+
"•"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def list_node_name_for_li_node(node)
|
78
|
+
node.ancestors.lazy.map(&:name).grep(/^[uo]l$/).first
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionText
|
4
|
+
module Serialization
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class_methods do
|
8
|
+
def load(content)
|
9
|
+
new(content) if content
|
10
|
+
end
|
11
|
+
|
12
|
+
def dump(content)
|
13
|
+
case content
|
14
|
+
when nil
|
15
|
+
nil
|
16
|
+
when self
|
17
|
+
content.to_html
|
18
|
+
else
|
19
|
+
new(content).to_html
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Marshal compatibility
|
25
|
+
|
26
|
+
class_methods do
|
27
|
+
alias_method :_load, :load
|
28
|
+
end
|
29
|
+
|
30
|
+
def _dump(*)
|
31
|
+
self.class.dump(self)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionText
|
4
|
+
class TrixAttachment
|
5
|
+
TAG_NAME = "figure"
|
6
|
+
SELECTOR = "[data-trix-attachment]"
|
7
|
+
|
8
|
+
COMPOSED_ATTRIBUTES = %w( caption presentation )
|
9
|
+
ATTRIBUTES = %w( sgid contentType url href filename filesize width height previewable content ) + COMPOSED_ATTRIBUTES
|
10
|
+
ATTRIBUTE_TYPES = {
|
11
|
+
"previewable" => ->(value) { value.to_s == "true" },
|
12
|
+
"filesize" => ->(value) { Integer(value.to_s) rescue value },
|
13
|
+
"width" => ->(value) { Integer(value.to_s) rescue nil },
|
14
|
+
"height" => ->(value) { Integer(value.to_s) rescue nil },
|
15
|
+
:default => ->(value) { value.to_s }
|
16
|
+
}
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def from_attributes(attributes)
|
20
|
+
attributes = process_attributes(attributes)
|
21
|
+
|
22
|
+
trix_attachment_attributes = attributes.except(*COMPOSED_ATTRIBUTES)
|
23
|
+
trix_attributes = attributes.slice(*COMPOSED_ATTRIBUTES)
|
24
|
+
|
25
|
+
node = ActionText::HtmlConversion.create_element(TAG_NAME)
|
26
|
+
node["data-trix-attachment"] = JSON.generate(trix_attachment_attributes)
|
27
|
+
node["data-trix-attributes"] = JSON.generate(trix_attributes) if trix_attributes.any?
|
28
|
+
|
29
|
+
new(node)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def process_attributes(attributes)
|
34
|
+
typecast_attribute_values(transform_attribute_keys(attributes))
|
35
|
+
end
|
36
|
+
|
37
|
+
def transform_attribute_keys(attributes)
|
38
|
+
attributes.transform_keys { |key| key.to_s.underscore.camelize(:lower) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def typecast_attribute_values(attributes)
|
42
|
+
attributes.map do |key, value|
|
43
|
+
typecast = ATTRIBUTE_TYPES[key] || ATTRIBUTE_TYPES[:default]
|
44
|
+
[key, typecast.call(value)]
|
45
|
+
end.to_h
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_reader :node
|
50
|
+
|
51
|
+
def initialize(node)
|
52
|
+
@node = node
|
53
|
+
end
|
54
|
+
|
55
|
+
def attributes
|
56
|
+
@attributes ||= attachment_attributes.merge(composed_attributes).slice(*ATTRIBUTES)
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_html
|
60
|
+
ActionText::HtmlConversion.node_to_html(node)
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_s
|
64
|
+
to_html
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def attachment_attributes
|
69
|
+
read_json_object_attribute("data-trix-attachment")
|
70
|
+
end
|
71
|
+
|
72
|
+
def composed_attributes
|
73
|
+
read_json_object_attribute("data-trix-attributes")
|
74
|
+
end
|
75
|
+
|
76
|
+
def read_json_object_attribute(name)
|
77
|
+
read_json_attribute(name) || {}
|
78
|
+
end
|
79
|
+
|
80
|
+
def read_json_attribute(name)
|
81
|
+
if value = node[name]
|
82
|
+
begin
|
83
|
+
JSON.parse(value)
|
84
|
+
rescue => e
|
85
|
+
Rails.logger.error "[#{self.class.name}] Couldn't parse JSON #{value} from NODE #{node.inspect}"
|
86
|
+
Rails.logger.error "[#{self.class.name}] Failed with #{e.class}: #{e.backtrace}"
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :action_text do
|
4
|
+
# Prevent migration installation task from showing up twice.
|
5
|
+
Rake::Task["install:migrations"].clear_comments
|
6
|
+
|
7
|
+
desc "Copy over the migration, stylesheet, and JavaScript files"
|
8
|
+
task install: %w( environment run_installer copy_migrations )
|
9
|
+
|
10
|
+
task :run_installer do
|
11
|
+
installer_template = File.expand_path("../templates/installer.rb", __dir__)
|
12
|
+
system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{installer_template}"
|
13
|
+
end
|
14
|
+
|
15
|
+
task :copy_migrations do
|
16
|
+
Rake::Task["active_storage:install:migrations"].invoke
|
17
|
+
Rake::Task["railties:install:migrations"].reenable # Otherwise you can't run 2 migration copy tasks in one invocation
|
18
|
+
Rake::Task["action_text:install:migrations"].invoke
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
//
|
2
|
+
// Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
|
3
|
+
// the trix-editor content (whether displayed or under editing). Feel free to incorporate this
|
4
|
+
// inclusion directly in any other asset bundle and remove this file.
|
5
|
+
//
|
6
|
+
//= require trix/dist/trix
|
7
|
+
|
8
|
+
// We need to override trix.css’s image gallery styles to accommodate the
|
9
|
+
// <action-text-attachment> element we wrap around attachments. Otherwise,
|
10
|
+
// images in galleries will be squished by the max-width: 33%; rule.
|
11
|
+
.trix-content {
|
12
|
+
.attachment-gallery {
|
13
|
+
> action-text-attachment,
|
14
|
+
> .attachment {
|
15
|
+
flex: 1 0 33%;
|
16
|
+
padding: 0 0.5em;
|
17
|
+
max-width: 33%;
|
18
|
+
}
|
19
|
+
|
20
|
+
&.attachment-gallery--2,
|
21
|
+
&.attachment-gallery--4 {
|
22
|
+
> action-text-attachment,
|
23
|
+
> .attachment {
|
24
|
+
flex-basis: 50%;
|
25
|
+
max-width: 50%;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
action-text-attachment {
|
31
|
+
.attachment {
|
32
|
+
padding: 0 !important;
|
33
|
+
max-width: 100% !important;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
APPLICATION_PACK_PATH = Pathname.new("app/javascript/packs/application.js")
|
5
|
+
JS_PACKAGE_PATH = Pathname.new("#{__dir__}/../../package.json")
|
6
|
+
|
7
|
+
JS_PACKAGE = JSON.load(JS_PACKAGE_PATH)
|
8
|
+
JS_DEPENDENCIES = JS_PACKAGE["peerDependencies"].dup.merge \
|
9
|
+
JS_PACKAGE["name"] => "^#{JS_PACKAGE["version"]}"
|
10
|
+
|
11
|
+
say "Copying actiontext.scss to app/assets/stylesheets"
|
12
|
+
copy_file "#{__dir__}/actiontext.scss", "app/assets/stylesheets/actiontext.scss"
|
13
|
+
|
14
|
+
say "Copying fixtures to test/fixtures/action_text/rich_texts.yml"
|
15
|
+
copy_file "#{__dir__}/fixtures.yml", "test/fixtures/action_text/rich_texts.yml"
|
16
|
+
|
17
|
+
say "Copying blob rendering partial to app/views/active_storage/blobs/_blob.html.erb"
|
18
|
+
copy_file "#{__dir__}/../../app/views/active_storage/blobs/_blob.html.erb",
|
19
|
+
"app/views/active_storage/blobs/_blob.html.erb"
|
20
|
+
|
21
|
+
say "Installing JavaScript dependencies"
|
22
|
+
run "yarn add #{JS_DEPENDENCIES.map { |name, version| "#{name}@#{version}" }.join(" ")}"
|
23
|
+
|
24
|
+
if APPLICATION_PACK_PATH.exist?
|
25
|
+
JS_DEPENDENCIES.keys.each do |name|
|
26
|
+
line = %[require("#{name}")]
|
27
|
+
unless APPLICATION_PACK_PATH.read.include? line
|
28
|
+
say "Adding #{name} to #{APPLICATION_PACK_PATH}"
|
29
|
+
append_to_file APPLICATION_PACK_PATH, "\n#{line}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
else
|
33
|
+
warn <<~WARNING
|
34
|
+
WARNING: Action Text can't locate your JavaScript bundle to add its package dependencies.
|
35
|
+
|
36
|
+
Add these lines to any bundles:
|
37
|
+
|
38
|
+
require("trix")
|
39
|
+
require("@rails/actiontext")
|
40
|
+
|
41
|
+
Alternatively, install and setup the webpacker gem then rerun `bin/rails action_text:install`
|
42
|
+
to have these dependencies added automatically.
|
43
|
+
|
44
|
+
WARNING
|
45
|
+
end
|