omg-actiontext 8.0.0.alpha3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +42 -0
- data/MIT-LICENSE +21 -0
- data/README.md +13 -0
- data/app/assets/javascripts/actiontext.esm.js +911 -0
- data/app/assets/javascripts/actiontext.js +884 -0
- data/app/assets/javascripts/trix.js +12165 -0
- data/app/assets/stylesheets/trix.css +412 -0
- data/app/helpers/action_text/content_helper.rb +76 -0
- data/app/helpers/action_text/tag_helper.rb +106 -0
- data/app/javascript/actiontext/attachment_upload.js +62 -0
- data/app/javascript/actiontext/index.js +10 -0
- data/app/models/action_text/encrypted_rich_text.rb +11 -0
- data/app/models/action_text/record.rb +11 -0
- data/app/models/action_text/rich_text.rb +93 -0
- data/app/views/action_text/attachables/_content_attachment.html.erb +3 -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/contents/_content.html.erb +1 -0
- data/app/views/active_storage/blobs/_blob.html.erb +14 -0
- data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
- data/db/migrate/20180528164100_create_action_text_tables.rb +25 -0
- data/lib/action_text/attachable.rb +156 -0
- data/lib/action_text/attachables/content_attachment.rb +42 -0
- data/lib/action_text/attachables/missing_attachable.rb +29 -0
- data/lib/action_text/attachables/remote_image.rb +48 -0
- data/lib/action_text/attachment.rb +148 -0
- data/lib/action_text/attachment_gallery.rb +72 -0
- data/lib/action_text/attachments/caching.rb +18 -0
- data/lib/action_text/attachments/minification.rb +19 -0
- data/lib/action_text/attachments/trix_conversion.rb +38 -0
- data/lib/action_text/attribute.rb +105 -0
- data/lib/action_text/content.rb +197 -0
- data/lib/action_text/deprecator.rb +9 -0
- data/lib/action_text/encryption.rb +40 -0
- data/lib/action_text/engine.rb +94 -0
- data/lib/action_text/fixture_set.rb +68 -0
- data/lib/action_text/fragment.rb +62 -0
- data/lib/action_text/gem_version.rb +19 -0
- data/lib/action_text/html_conversion.rb +26 -0
- data/lib/action_text/plain_text_conversion.rb +114 -0
- data/lib/action_text/rendering.rb +35 -0
- data/lib/action_text/serialization.rb +38 -0
- data/lib/action_text/system_test_helper.rb +61 -0
- data/lib/action_text/trix_attachment.rb +94 -0
- data/lib/action_text/version.rb +12 -0
- data/lib/action_text.rb +59 -0
- data/lib/generators/action_text/install/install_generator.rb +84 -0
- data/lib/generators/action_text/install/templates/actiontext.css +440 -0
- data/lib/rails/generators/test_unit/install_generator.rb +15 -0
- data/lib/rails/generators/test_unit/templates/fixtures.yml +4 -0
- data/lib/tasks/actiontext.rake +6 -0
- data/package.json +39 -0
- metadata +190 -0
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionText
|
6
|
+
module PlainTextConversion
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def node_to_plain_text(node)
|
10
|
+
remove_trailing_newlines(plain_text_for_node(node))
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def plain_text_for_node(node, index = 0)
|
15
|
+
if respond_to?(plain_text_method_for_node(node), true)
|
16
|
+
send(plain_text_method_for_node(node), node, index)
|
17
|
+
else
|
18
|
+
plain_text_for_node_children(node)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def plain_text_for_node_children(node)
|
23
|
+
texts = []
|
24
|
+
node.children.each_with_index do |child, index|
|
25
|
+
texts << plain_text_for_node(child, index)
|
26
|
+
end
|
27
|
+
texts.join
|
28
|
+
end
|
29
|
+
|
30
|
+
def plain_text_method_for_node(node)
|
31
|
+
:"plain_text_for_#{node.name}_node"
|
32
|
+
end
|
33
|
+
|
34
|
+
def plain_text_for_block(node, index = 0)
|
35
|
+
"#{remove_trailing_newlines(plain_text_for_node_children(node))}\n\n"
|
36
|
+
end
|
37
|
+
|
38
|
+
%i[ h1 p ].each do |element|
|
39
|
+
alias_method :"plain_text_for_#{element}_node", :plain_text_for_block
|
40
|
+
end
|
41
|
+
|
42
|
+
def plain_text_for_list(node, index)
|
43
|
+
"#{break_if_nested_list(node, plain_text_for_block(node))}"
|
44
|
+
end
|
45
|
+
|
46
|
+
%i[ ul ol ].each do |element|
|
47
|
+
alias_method :"plain_text_for_#{element}_node", :plain_text_for_list
|
48
|
+
end
|
49
|
+
|
50
|
+
def plain_text_for_br_node(node, index)
|
51
|
+
"\n"
|
52
|
+
end
|
53
|
+
|
54
|
+
def plain_text_for_text_node(node, index)
|
55
|
+
remove_trailing_newlines(node.text)
|
56
|
+
end
|
57
|
+
|
58
|
+
def plain_text_for_div_node(node, index)
|
59
|
+
"#{remove_trailing_newlines(plain_text_for_node_children(node))}\n"
|
60
|
+
end
|
61
|
+
|
62
|
+
def plain_text_for_figcaption_node(node, index)
|
63
|
+
"[#{remove_trailing_newlines(plain_text_for_node_children(node))}]"
|
64
|
+
end
|
65
|
+
|
66
|
+
def plain_text_for_blockquote_node(node, index)
|
67
|
+
text = plain_text_for_block(node)
|
68
|
+
text.sub(/\A(\s*)(.+?)(\s*)\Z/m, '\1“\2”\3')
|
69
|
+
end
|
70
|
+
|
71
|
+
def plain_text_for_li_node(node, index)
|
72
|
+
bullet = bullet_for_li_node(node, index)
|
73
|
+
text = remove_trailing_newlines(plain_text_for_node_children(node))
|
74
|
+
indentation = indentation_for_li_node(node)
|
75
|
+
|
76
|
+
"#{indentation}#{bullet} #{text}\n"
|
77
|
+
end
|
78
|
+
|
79
|
+
def remove_trailing_newlines(text)
|
80
|
+
text.chomp("")
|
81
|
+
end
|
82
|
+
|
83
|
+
def bullet_for_li_node(node, index)
|
84
|
+
if list_node_name_for_li_node(node) == "ol"
|
85
|
+
"#{index + 1}."
|
86
|
+
else
|
87
|
+
"•"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def list_node_name_for_li_node(node)
|
92
|
+
node.ancestors.lazy.map(&:name).grep(/^[uo]l$/).first
|
93
|
+
end
|
94
|
+
|
95
|
+
def indentation_for_li_node(node)
|
96
|
+
depth = list_node_depth_for_node(node)
|
97
|
+
if depth > 1
|
98
|
+
" " * (depth - 1)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def list_node_depth_for_node(node)
|
103
|
+
node.ancestors.map(&:name).grep(/^[uo]l$/).count
|
104
|
+
end
|
105
|
+
|
106
|
+
def break_if_nested_list(node, text)
|
107
|
+
if list_node_depth_for_node(node) > 0
|
108
|
+
"\n#{text}"
|
109
|
+
else
|
110
|
+
text
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "active_support/concern"
|
6
|
+
require "active_support/core_ext/module/attribute_accessors_per_thread"
|
7
|
+
|
8
|
+
module ActionText
|
9
|
+
module Rendering # :nodoc:
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
included do
|
13
|
+
thread_cattr_accessor :renderer, instance_accessor: false
|
14
|
+
delegate :render, to: :class
|
15
|
+
end
|
16
|
+
|
17
|
+
class_methods do
|
18
|
+
def action_controller_renderer
|
19
|
+
@action_controller_renderer ||= Class.new(ActionController::Base).renderer
|
20
|
+
end
|
21
|
+
|
22
|
+
def with_renderer(renderer)
|
23
|
+
previous_renderer = self.renderer
|
24
|
+
self.renderer = renderer
|
25
|
+
yield
|
26
|
+
ensure
|
27
|
+
self.renderer = previous_renderer
|
28
|
+
end
|
29
|
+
|
30
|
+
def render(*args, &block)
|
31
|
+
(renderer || action_controller_renderer).render_to_string(*args, &block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionText
|
6
|
+
module Serialization
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
class_methods do
|
10
|
+
def load(content)
|
11
|
+
new(content) if content
|
12
|
+
end
|
13
|
+
|
14
|
+
def dump(content)
|
15
|
+
case content
|
16
|
+
when nil
|
17
|
+
nil
|
18
|
+
when self
|
19
|
+
content.to_html
|
20
|
+
when ActionText::RichText
|
21
|
+
content.body.to_html
|
22
|
+
else
|
23
|
+
new(content).to_html
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Marshal compatibility
|
29
|
+
|
30
|
+
class_methods do
|
31
|
+
alias_method :_load, :load
|
32
|
+
end
|
33
|
+
|
34
|
+
def _dump(*)
|
35
|
+
self.class.dump(self)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionText
|
6
|
+
module SystemTestHelper
|
7
|
+
# Locates a Trix editor and fills it in with the given HTML.
|
8
|
+
#
|
9
|
+
# The editor can be found by:
|
10
|
+
# * its `id`
|
11
|
+
# * its `placeholder`
|
12
|
+
# * the text from its `label` element
|
13
|
+
# * its `aria-label`
|
14
|
+
# * the `name` of its input
|
15
|
+
#
|
16
|
+
#
|
17
|
+
# Examples:
|
18
|
+
#
|
19
|
+
# # <trix-editor id="message_content" ...></trix-editor>
|
20
|
+
# fill_in_rich_textarea "message_content", with: "Hello <em>world!</em>"
|
21
|
+
#
|
22
|
+
# # <trix-editor placeholder="Your message here" ...></trix-editor>
|
23
|
+
# fill_in_rich_textarea "Your message here", with: "Hello <em>world!</em>"
|
24
|
+
#
|
25
|
+
# # <label for="message_content">Message content</label>
|
26
|
+
# # <trix-editor id="message_content" ...></trix-editor>
|
27
|
+
# fill_in_rich_textarea "Message content", with: "Hello <em>world!</em>"
|
28
|
+
#
|
29
|
+
# # <trix-editor aria-label="Message content" ...></trix-editor>
|
30
|
+
# fill_in_rich_textarea "Message content", with: "Hello <em>world!</em>"
|
31
|
+
#
|
32
|
+
# # <input id="trix_input_1" name="message[content]" type="hidden">
|
33
|
+
# # <trix-editor input="trix_input_1"></trix-editor>
|
34
|
+
# fill_in_rich_textarea "message[content]", with: "Hello <em>world!</em>"
|
35
|
+
def fill_in_rich_textarea(locator = nil, with:)
|
36
|
+
find(:rich_textarea, locator).execute_script("this.editor.loadHTML(arguments[0])", with.to_s)
|
37
|
+
end
|
38
|
+
alias_method :fill_in_rich_text_area, :fill_in_rich_textarea
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
%i[rich_textarea rich_text_area].each do |rich_textarea|
|
43
|
+
Capybara.add_selector rich_textarea do
|
44
|
+
label "rich-text area"
|
45
|
+
xpath do |locator|
|
46
|
+
if locator.nil?
|
47
|
+
XPath.descendant(:"trix-editor")
|
48
|
+
else
|
49
|
+
input_located_by_name = XPath.anywhere(:input).where(XPath.attr(:name) == locator).attr(:id)
|
50
|
+
input_located_by_label = XPath.anywhere(:label).where(XPath.string.n.is(locator)).attr(:for)
|
51
|
+
|
52
|
+
XPath.descendant(:"trix-editor").where \
|
53
|
+
XPath.attr(:id).equals(locator) |
|
54
|
+
XPath.attr(:placeholder).equals(locator) |
|
55
|
+
XPath.attr(:"aria-label").equals(locator) |
|
56
|
+
XPath.attr(:input).equals(input_located_by_name) |
|
57
|
+
XPath.attr(:id).equals(input_located_by_label)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionText
|
6
|
+
class TrixAttachment
|
7
|
+
TAG_NAME = "figure"
|
8
|
+
SELECTOR = "[data-trix-attachment]"
|
9
|
+
|
10
|
+
COMPOSED_ATTRIBUTES = %w( caption presentation )
|
11
|
+
ATTRIBUTES = %w( sgid contentType url href filename filesize width height previewable content ) + COMPOSED_ATTRIBUTES
|
12
|
+
ATTRIBUTE_TYPES = {
|
13
|
+
"previewable" => ->(value) { value.to_s == "true" },
|
14
|
+
"filesize" => ->(value) { Integer(value.to_s, exception: false) || value },
|
15
|
+
"width" => ->(value) { Integer(value.to_s, exception: false) },
|
16
|
+
"height" => ->(value) { Integer(value.to_s, exception: false) },
|
17
|
+
:default => ->(value) { value.to_s }
|
18
|
+
}
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def from_attributes(attributes)
|
22
|
+
attributes = process_attributes(attributes)
|
23
|
+
|
24
|
+
trix_attachment_attributes = attributes.except(*COMPOSED_ATTRIBUTES)
|
25
|
+
trix_attributes = attributes.slice(*COMPOSED_ATTRIBUTES)
|
26
|
+
|
27
|
+
node = ActionText::HtmlConversion.create_element(TAG_NAME)
|
28
|
+
node["data-trix-attachment"] = JSON.generate(trix_attachment_attributes)
|
29
|
+
node["data-trix-attributes"] = JSON.generate(trix_attributes) if trix_attributes.any?
|
30
|
+
|
31
|
+
new(node)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def process_attributes(attributes)
|
36
|
+
typecast_attribute_values(transform_attribute_keys(attributes))
|
37
|
+
end
|
38
|
+
|
39
|
+
def transform_attribute_keys(attributes)
|
40
|
+
attributes.transform_keys { |key| key.to_s.underscore.camelize(:lower) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def typecast_attribute_values(attributes)
|
44
|
+
attributes.to_h do |key, value|
|
45
|
+
typecast = ATTRIBUTE_TYPES[key] || ATTRIBUTE_TYPES[:default]
|
46
|
+
[key, typecast.call(value)]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_reader :node
|
52
|
+
|
53
|
+
def initialize(node)
|
54
|
+
@node = node
|
55
|
+
end
|
56
|
+
|
57
|
+
def attributes
|
58
|
+
@attributes ||= attachment_attributes.merge(composed_attributes).slice(*ATTRIBUTES)
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_html
|
62
|
+
ActionText::HtmlConversion.node_to_html(node)
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
to_html
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def attachment_attributes
|
71
|
+
read_json_object_attribute("data-trix-attachment")
|
72
|
+
end
|
73
|
+
|
74
|
+
def composed_attributes
|
75
|
+
read_json_object_attribute("data-trix-attributes")
|
76
|
+
end
|
77
|
+
|
78
|
+
def read_json_object_attribute(name)
|
79
|
+
read_json_attribute(name) || {}
|
80
|
+
end
|
81
|
+
|
82
|
+
def read_json_attribute(name)
|
83
|
+
if value = node[name]
|
84
|
+
begin
|
85
|
+
JSON.parse(value)
|
86
|
+
rescue => e
|
87
|
+
Rails.logger.error "[#{self.class.name}] Couldn't parse JSON #{value} from NODE #{node.inspect}"
|
88
|
+
Rails.logger.error "[#{self.class.name}] Failed with #{e.class}: #{e.backtrace}"
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/action_text.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support"
|
4
|
+
require "active_support/rails"
|
5
|
+
|
6
|
+
require "action_text/version"
|
7
|
+
require "action_text/deprecator"
|
8
|
+
|
9
|
+
require "nokogiri"
|
10
|
+
|
11
|
+
# :markup: markdown
|
12
|
+
# :include: ../README.md
|
13
|
+
module ActionText
|
14
|
+
extend ActiveSupport::Autoload
|
15
|
+
|
16
|
+
autoload :Attachable
|
17
|
+
autoload :AttachmentGallery
|
18
|
+
autoload :Attachment
|
19
|
+
autoload :Attribute
|
20
|
+
autoload :Content
|
21
|
+
autoload :Encryption
|
22
|
+
autoload :Fragment
|
23
|
+
autoload :FixtureSet
|
24
|
+
autoload :HtmlConversion
|
25
|
+
autoload :PlainTextConversion
|
26
|
+
autoload :Rendering
|
27
|
+
autoload :Serialization
|
28
|
+
autoload :TrixAttachment
|
29
|
+
|
30
|
+
module Attachables
|
31
|
+
extend ActiveSupport::Autoload
|
32
|
+
|
33
|
+
autoload :ContentAttachment
|
34
|
+
autoload :MissingAttachable
|
35
|
+
autoload :RemoteImage
|
36
|
+
end
|
37
|
+
|
38
|
+
module Attachments
|
39
|
+
extend ActiveSupport::Autoload
|
40
|
+
|
41
|
+
autoload :Caching
|
42
|
+
autoload :Minification
|
43
|
+
autoload :TrixConversion
|
44
|
+
end
|
45
|
+
|
46
|
+
class << self
|
47
|
+
def html_document_class
|
48
|
+
return @html_document_class if defined?(@html_document_class)
|
49
|
+
@html_document_class =
|
50
|
+
defined?(Nokogiri::HTML5) ? Nokogiri::HTML5::Document : Nokogiri::HTML4::Document
|
51
|
+
end
|
52
|
+
|
53
|
+
def html_document_fragment_class
|
54
|
+
return @html_document_fragment_class if defined?(@html_document_fragment_class)
|
55
|
+
@html_document_fragment_class =
|
56
|
+
defined?(Nokogiri::HTML5) ? Nokogiri::HTML5::DocumentFragment : Nokogiri::HTML4::DocumentFragment
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "pathname"
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
module ActionText
|
9
|
+
module Generators
|
10
|
+
class InstallGenerator < ::Rails::Generators::Base
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
12
|
+
|
13
|
+
def install_javascript_dependencies
|
14
|
+
say "Installing JavaScript dependencies", :green
|
15
|
+
if using_bun?
|
16
|
+
run "bun add @rails/actiontext trix"
|
17
|
+
elsif using_node?
|
18
|
+
run "yarn add @rails/actiontext trix"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def append_javascript_dependencies
|
23
|
+
destination = Pathname(destination_root)
|
24
|
+
|
25
|
+
if (application_javascript_path = destination.join("app/javascript/application.js")).exist?
|
26
|
+
insert_into_file application_javascript_path.to_s, %(\nimport "trix"\nimport "@rails/actiontext"\n)
|
27
|
+
else
|
28
|
+
say <<~INSTRUCTIONS, :green
|
29
|
+
You must import the @rails/actiontext and trix JavaScript modules in your application entrypoint.
|
30
|
+
INSTRUCTIONS
|
31
|
+
end
|
32
|
+
|
33
|
+
if (importmap_path = destination.join("config/importmap.rb")).exist?
|
34
|
+
append_to_file importmap_path.to_s, %(pin "trix"\npin "@rails/actiontext", to: "actiontext.esm.js"\n)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_actiontext_files
|
39
|
+
template "actiontext.css", "app/assets/stylesheets/actiontext.css"
|
40
|
+
|
41
|
+
gem_root = "#{__dir__}/../../../.."
|
42
|
+
|
43
|
+
copy_file "#{gem_root}/app/views/active_storage/blobs/_blob.html.erb",
|
44
|
+
"app/views/active_storage/blobs/_blob.html.erb"
|
45
|
+
|
46
|
+
copy_file "#{gem_root}/app/views/layouts/action_text/contents/_content.html.erb",
|
47
|
+
"app/views/layouts/action_text/contents/_content.html.erb"
|
48
|
+
end
|
49
|
+
|
50
|
+
def enable_image_processing_gem
|
51
|
+
if (gemfile_path = Pathname(destination_root).join("Gemfile")).exist?
|
52
|
+
say "Ensure image_processing gem has been enabled so image uploads will work (remember to bundle!)"
|
53
|
+
image_processing_regex = /gem ["']image_processing["']/
|
54
|
+
if File.readlines(gemfile_path).grep(image_processing_regex).any?
|
55
|
+
uncomment_lines gemfile_path, image_processing_regex
|
56
|
+
else
|
57
|
+
run "bundle add --skip-install image_processing"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def create_migrations
|
63
|
+
rails_command "railties:install:migrations FROM=active_storage,action_text", inline: true
|
64
|
+
end
|
65
|
+
|
66
|
+
def using_js_runtime?
|
67
|
+
@using_js_runtime ||= Pathname(destination_root).join("package.json").exist?
|
68
|
+
end
|
69
|
+
|
70
|
+
def using_bun?
|
71
|
+
# Cannot assume yarn.lock has been generated yet so we look for a file known to
|
72
|
+
# be generated by the jsbundling-rails gem
|
73
|
+
@using_bun ||= using_js_runtime? && Pathname(destination_root).join("bun.config.js").exist?
|
74
|
+
end
|
75
|
+
|
76
|
+
def using_node?
|
77
|
+
# Bun is the only runtime that _isn't_ node.
|
78
|
+
@using_node ||= using_js_runtime? && !Pathname(destination_root).join("bun.config.js").exist?
|
79
|
+
end
|
80
|
+
|
81
|
+
hook_for :test_framework
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|