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.

Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +40 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.md +9 -0
  5. data/app/helpers/action_text/content_helper.rb +37 -0
  6. data/app/helpers/action_text/tag_helper.rb +79 -0
  7. data/app/javascript/actiontext/attachment_upload.js +45 -0
  8. data/app/javascript/actiontext/index.js +10 -0
  9. data/app/models/action_text/rich_text.rb +29 -0
  10. data/app/views/action_text/attachables/_missing_attachable.html.erb +1 -0
  11. data/app/views/action_text/attachables/_remote_image.html.erb +8 -0
  12. data/app/views/action_text/attachment_galleries/_attachment_gallery.html.erb +3 -0
  13. data/app/views/action_text/content/_layout.html.erb +3 -0
  14. data/app/views/active_storage/blobs/_blob.html.erb +14 -0
  15. data/db/migrate/20180528164100_create_action_text_tables.rb +13 -0
  16. data/lib/action_text.rb +37 -0
  17. data/lib/action_text/attachable.rb +86 -0
  18. data/lib/action_text/attachables/content_attachment.rb +38 -0
  19. data/lib/action_text/attachables/missing_attachable.rb +13 -0
  20. data/lib/action_text/attachables/remote_image.rb +46 -0
  21. data/lib/action_text/attachment.rb +103 -0
  22. data/lib/action_text/attachment_gallery.rb +65 -0
  23. data/lib/action_text/attachments/caching.rb +16 -0
  24. data/lib/action_text/attachments/minification.rb +17 -0
  25. data/lib/action_text/attachments/trix_conversion.rb +34 -0
  26. data/lib/action_text/attribute.rb +45 -0
  27. data/lib/action_text/content.rb +132 -0
  28. data/lib/action_text/engine.rb +54 -0
  29. data/lib/action_text/fragment.rb +57 -0
  30. data/lib/action_text/gem_version.rb +17 -0
  31. data/lib/action_text/html_conversion.rb +24 -0
  32. data/lib/action_text/plain_text_conversion.rb +81 -0
  33. data/lib/action_text/serialization.rb +34 -0
  34. data/lib/action_text/trix_attachment.rb +92 -0
  35. data/lib/action_text/version.rb +10 -0
  36. data/lib/tasks/actiontext.rake +20 -0
  37. data/lib/templates/actiontext.scss +36 -0
  38. data/lib/templates/fixtures.yml +4 -0
  39. data/lib/templates/installer.rb +45 -0
  40. data/package.json +29 -0
  41. metadata +161 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fffc535dc4d4e4ac11618e285a6baba17ac695b10792a8cfe5e057c9b9ff5556
4
+ data.tar.gz: f2fad3e29b69077e3df2b03c4d56beff85467e6097451dda4b26eaf5f9154812
5
+ SHA512:
6
+ metadata.gz: f5f76ff561433ee6794151bdb5944f642db2bb3d7841e9f341a35ce9443f331233ad4666aa34ae1b31ff3777fdb5f6ae8280b55bdca2d69601082682168dec33
7
+ data.tar.gz: '02581c676e47a678c67574af15b354fc82c7902f89c5934f84424826c2717852f425d53b970633bb5f2143873989f7e7d396085d47a169622f5846a3f2ae265a'
@@ -0,0 +1,40 @@
1
+ ## Rails 6.0.2 (December 13, 2019) ##
2
+
3
+ * No changes.
4
+
5
+
6
+ ## Rails 6.0.1 (November 5, 2019) ##
7
+
8
+ * No changes.
9
+
10
+
11
+ ## Rails 6.0.0 (August 16, 2019) ##
12
+
13
+ * No changes.
14
+
15
+
16
+ ## Rails 6.0.0.rc2 (July 22, 2019) ##
17
+
18
+ * No changes.
19
+
20
+
21
+ ## Rails 6.0.0.rc1 (April 24, 2019) ##
22
+
23
+ * No changes.
24
+
25
+
26
+ ## Rails 6.0.0.beta3 (March 11, 2019) ##
27
+
28
+ * No changes.
29
+
30
+
31
+ ## Rails 6.0.0.beta2 (February 25, 2019) ##
32
+
33
+ * No changes.
34
+
35
+
36
+ ## Rails 6.0.0.beta1 (January 18, 2019) ##
37
+
38
+ * Added to Rails.
39
+
40
+ *DHH*
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Basecamp, LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ # Action Text
2
+
3
+ Action Text brings rich text content and editing to Rails. It includes the [Trix editor](https://trix-editor.org) that handles everything from formatting to links to quotes to lists to embedded images and galleries. The rich text content generated by the Trix editor is saved in its own RichText model that's associated with any existing Active Record model in the application. Any embedded images (or other attachments) are automatically stored using Active Storage and associated with the included RichText model.
4
+
5
+ You can read more about Action Text in the [Action Text Overview](https://edgeguides.rubyonrails.org/action_text_overview.html) guide.
6
+
7
+ ## License
8
+
9
+ Action Text is released under the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails-html-sanitizer"
4
+
5
+ module ActionText
6
+ module ContentHelper
7
+ mattr_accessor(:sanitizer) { Rails::Html::Sanitizer.safe_list_sanitizer.new }
8
+ mattr_accessor(:allowed_tags) { sanitizer.class.allowed_tags + [ ActionText::Attachment::TAG_NAME, "figure", "figcaption" ] }
9
+ mattr_accessor(:allowed_attributes) { sanitizer.class.allowed_attributes + ActionText::Attachment::ATTRIBUTES }
10
+ mattr_accessor(:scrubber)
11
+
12
+ def render_action_text_content(content)
13
+ sanitize_action_text_content(render_action_text_attachments(content))
14
+ end
15
+
16
+ def sanitize_action_text_content(content)
17
+ sanitizer.sanitize(content.to_html, tags: allowed_tags, attributes: allowed_attributes, scrubber: scrubber).html_safe
18
+ end
19
+
20
+ def render_action_text_attachments(content)
21
+ content.render_attachments do |attachment|
22
+ unless attachment.in?(content.gallery_attachments)
23
+ attachment.node.tap do |node|
24
+ node.inner_html = render(attachment, in_gallery: false).chomp
25
+ end
26
+ end
27
+ end.render_attachment_galleries do |attachment_gallery|
28
+ render(layout: attachment_gallery, object: attachment_gallery) do
29
+ attachment_gallery.attachments.map do |attachment|
30
+ attachment.node.inner_html = render(attachment, in_gallery: true).chomp
31
+ attachment.to_html
32
+ end.join("").html_safe
33
+ end.chomp
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tags/placeholderable"
4
+
5
+ module ActionText
6
+ module TagHelper
7
+ cattr_accessor(:id, instance_accessor: false) { 0 }
8
+
9
+ # Returns a +trix-editor+ tag that instantiates the Trix JavaScript editor as well as a hidden field
10
+ # that Trix will write to on changes, so the content will be sent on form submissions.
11
+ #
12
+ # ==== Options
13
+ # * <tt>:class</tt> - Defaults to "trix-content" which ensures default styling is applied.
14
+ #
15
+ # ==== Example
16
+ #
17
+ # rich_text_area_tag "content", message.content
18
+ # # <input type="hidden" name="content" id="trix_input_post_1">
19
+ # # <trix-editor id="content" input="trix_input_post_1" class="trix-content" ...></trix-editor>
20
+ def rich_text_area_tag(name, value = nil, options = {})
21
+ options = options.symbolize_keys
22
+
23
+ options[:input] ||= "trix_input_#{ActionText::TagHelper.id += 1}"
24
+ options[:class] ||= "trix-content"
25
+
26
+ options[:data] ||= {}
27
+ options[:data][:direct_upload_url] = main_app.rails_direct_uploads_url
28
+ options[:data][:blob_url_template] = main_app.rails_service_blob_url(":signed_id", ":filename")
29
+
30
+ editor_tag = content_tag("trix-editor", "", options)
31
+ input_tag = hidden_field_tag(name, value, id: options[:input])
32
+
33
+ input_tag + editor_tag
34
+ end
35
+ end
36
+ end
37
+
38
+ module ActionView::Helpers
39
+ class Tags::ActionText < Tags::Base
40
+ include Tags::Placeholderable
41
+
42
+ delegate :dom_id, to: ActionView::RecordIdentifier
43
+
44
+ def render
45
+ options = @options.stringify_keys
46
+ add_default_name_and_id(options)
47
+ options["input"] ||= dom_id(object, [options["id"], :trix_input].compact.join("_")) if object
48
+ @template_object.rich_text_area_tag(options.delete("name"), editable_value, options)
49
+ end
50
+
51
+ def editable_value
52
+ value&.body.try(:to_trix_html)
53
+ end
54
+ end
55
+
56
+ module FormHelper
57
+ # Returns a +trix-editor+ tag that instantiates the Trix JavaScript editor as well as a hidden field
58
+ # that Trix will write to on changes, so the content will be sent on form submissions.
59
+ #
60
+ # ==== Options
61
+ # * <tt>:class</tt> - Defaults to "trix-content" which ensures default styling is applied.
62
+ #
63
+ # ==== Example
64
+ # form_with(model: @message) do |form|
65
+ # form.rich_text_area :content
66
+ # end
67
+ # # <input type="hidden" name="message[content]" id="message_content_trix_input_message_1">
68
+ # # <trix-editor id="content" input="message_content_trix_input_message_1" class="trix-content" ...></trix-editor>
69
+ def rich_text_area(object_name, method, options = {})
70
+ Tags::ActionText.new(object_name, method, self, options).render
71
+ end
72
+ end
73
+
74
+ class FormBuilder
75
+ def rich_text_area(method, options = {})
76
+ @template.rich_text_area(@object_name, method, objectify_options(options))
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,45 @@
1
+ import { DirectUpload } from "@rails/activestorage"
2
+
3
+ export class AttachmentUpload {
4
+ constructor(attachment, element) {
5
+ this.attachment = attachment
6
+ this.element = element
7
+ this.directUpload = new DirectUpload(attachment.file, this.directUploadUrl, this)
8
+ }
9
+
10
+ start() {
11
+ this.directUpload.create(this.directUploadDidComplete.bind(this))
12
+ }
13
+
14
+ directUploadWillStoreFileWithXHR(xhr) {
15
+ xhr.upload.addEventListener("progress", event => {
16
+ const progress = event.loaded / event.total * 100
17
+ this.attachment.setUploadProgress(progress)
18
+ })
19
+ }
20
+
21
+ directUploadDidComplete(error, attributes) {
22
+ if (error) {
23
+ throw new Error(`Direct upload failed: ${error}`)
24
+ }
25
+
26
+ this.attachment.setAttributes({
27
+ sgid: attributes.attachable_sgid,
28
+ url: this.createBlobUrl(attributes.signed_id, attributes.filename)
29
+ })
30
+ }
31
+
32
+ createBlobUrl(signedId, filename) {
33
+ return this.blobUrlTemplate
34
+ .replace(":signed_id", signedId)
35
+ .replace(":filename", encodeURIComponent(filename))
36
+ }
37
+
38
+ get directUploadUrl() {
39
+ return this.element.dataset.directUploadUrl
40
+ }
41
+
42
+ get blobUrlTemplate() {
43
+ return this.element.dataset.blobUrlTemplate
44
+ }
45
+ }
@@ -0,0 +1,10 @@
1
+ import { AttachmentUpload } from "./attachment_upload"
2
+
3
+ addEventListener("trix-attachment-add", event => {
4
+ const { attachment, target } = event
5
+
6
+ if (attachment.file) {
7
+ const upload = new AttachmentUpload(attachment, target)
8
+ upload.start()
9
+ }
10
+ })
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionText
4
+ # The RichText record holds the content produced by the Trix editor in a serialized +body+ attribute.
5
+ # It also holds all the references to the embedded files, which are stored using Active Storage.
6
+ # This record is then associated with the Active Record model the application desires to have
7
+ # rich text content using the +has_rich_text+ class method.
8
+ class RichText < ActiveRecord::Base
9
+ self.table_name = "action_text_rich_texts"
10
+
11
+ serialize :body, ActionText::Content
12
+ delegate :to_s, :nil?, to: :body
13
+
14
+ belongs_to :record, polymorphic: true, touch: true
15
+ has_many_attached :embeds
16
+
17
+ before_save do
18
+ self.embeds = body.attachables.grep(ActiveStorage::Blob).uniq if body.present?
19
+ end
20
+
21
+ def to_plain_text
22
+ body&.to_plain_text.to_s
23
+ end
24
+
25
+ delegate :blank?, :empty?, :present?, to: :to_plain_text
26
+ end
27
+ end
28
+
29
+ ActiveSupport.run_load_hooks :action_text_rich_text, ActionText::RichText
@@ -0,0 +1,8 @@
1
+ <figure class="attachment attachment--preview">
2
+ <%= image_tag(remote_image.url, width: remote_image.width, height: remote_image.height) %>
3
+ <% if caption = remote_image.try(:caption) %>
4
+ <figcaption class="attachment__caption">
5
+ <%= caption %>
6
+ </figcaption>
7
+ <% end %>
8
+ </figure>
@@ -0,0 +1,3 @@
1
+ <div class="attachment-gallery attachment-gallery--<%= attachment_gallery.size %>">
2
+ <%= yield %>
3
+ </div>
@@ -0,0 +1,3 @@
1
+ <div class="trix-content">
2
+ <%= render_action_text_content(content) %>
3
+ </div>
@@ -0,0 +1,14 @@
1
+ <figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
2
+ <% if blob.representable? %>
3
+ <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
4
+ <% end %>
5
+
6
+ <figcaption class="attachment__caption">
7
+ <% if caption = blob.try(:caption) %>
8
+ <%= caption %>
9
+ <% else %>
10
+ <span class="attachment__name"><%= blob.filename %></span>
11
+ <span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
12
+ <% end %>
13
+ </figcaption>
14
+ </figure>
@@ -0,0 +1,13 @@
1
+ class CreateActionTextTables < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :action_text_rich_texts do |t|
4
+ t.string :name, null: false
5
+ t.text :body, size: :long
6
+ t.references :record, null: false, polymorphic: true, index: false
7
+
8
+ t.timestamps
9
+
10
+ t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "active_support/rails"
5
+
6
+ require "nokogiri"
7
+
8
+ module ActionText
9
+ extend ActiveSupport::Autoload
10
+
11
+ autoload :Attachable
12
+ autoload :AttachmentGallery
13
+ autoload :Attachment
14
+ autoload :Attribute
15
+ autoload :Content
16
+ autoload :Fragment
17
+ autoload :HtmlConversion
18
+ autoload :PlainTextConversion
19
+ autoload :Serialization
20
+ autoload :TrixAttachment
21
+
22
+ module Attachables
23
+ extend ActiveSupport::Autoload
24
+
25
+ autoload :ContentAttachment
26
+ autoload :MissingAttachable
27
+ autoload :RemoteImage
28
+ end
29
+
30
+ module Attachments
31
+ extend ActiveSupport::Autoload
32
+
33
+ autoload :Caching
34
+ autoload :Minification
35
+ autoload :TrixConversion
36
+ end
37
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionText
4
+ module Attachable
5
+ extend ActiveSupport::Concern
6
+
7
+ LOCATOR_NAME = "attachable"
8
+
9
+ class << self
10
+ def from_node(node)
11
+ if attachable = attachable_from_sgid(node["sgid"])
12
+ attachable
13
+ elsif attachable = ActionText::Attachables::ContentAttachment.from_node(node)
14
+ attachable
15
+ elsif attachable = ActionText::Attachables::RemoteImage.from_node(node)
16
+ attachable
17
+ else
18
+ ActionText::Attachables::MissingAttachable
19
+ end
20
+ end
21
+
22
+ def from_attachable_sgid(sgid, options = {})
23
+ method = sgid.is_a?(Array) ? :locate_many_signed : :locate_signed
24
+ record = GlobalID::Locator.public_send(method, sgid, options.merge(for: LOCATOR_NAME))
25
+ record || raise(ActiveRecord::RecordNotFound)
26
+ end
27
+
28
+ private
29
+ def attachable_from_sgid(sgid)
30
+ from_attachable_sgid(sgid)
31
+ rescue ActiveRecord::RecordNotFound
32
+ nil
33
+ end
34
+ end
35
+
36
+ class_methods do
37
+ def from_attachable_sgid(sgid)
38
+ ActionText::Attachable.from_attachable_sgid(sgid, only: self)
39
+ end
40
+ end
41
+
42
+ def attachable_sgid
43
+ to_sgid(expires_in: nil, for: LOCATOR_NAME).to_s
44
+ end
45
+
46
+ def attachable_content_type
47
+ try(:content_type) || "application/octet-stream"
48
+ end
49
+
50
+ def attachable_filename
51
+ filename.to_s if respond_to?(:filename)
52
+ end
53
+
54
+ def attachable_filesize
55
+ try(:byte_size) || try(:filesize)
56
+ end
57
+
58
+ def attachable_metadata
59
+ try(:metadata) || {}
60
+ end
61
+
62
+ def previewable_attachable?
63
+ false
64
+ end
65
+
66
+ def as_json(*)
67
+ super.merge(attachable_sgid: attachable_sgid)
68
+ end
69
+
70
+ def to_trix_content_attachment_partial_path
71
+ to_partial_path
72
+ end
73
+
74
+ def to_rich_text_attributes(attributes = {})
75
+ attributes.dup.tap do |attrs|
76
+ attrs[:sgid] = attachable_sgid
77
+ attrs[:content_type] = attachable_content_type
78
+ attrs[:previewable] = true if previewable_attachable?
79
+ attrs[:filename] = attachable_filename
80
+ attrs[:filesize] = attachable_filesize
81
+ attrs[:width] = attachable_metadata[:width]
82
+ attrs[:height] = attachable_metadata[:height]
83
+ end.compact
84
+ end
85
+ end
86
+ end