diff options
-rw-r--r-- | Gemfile.lock | 4 | ||||
-rw-r--r-- | README.md | 20 | ||||
-rw-r--r-- | app/helpers/action_text/tag_helper.rb | 14 | ||||
-rw-r--r-- | app/models/action_text/rich_text.rb | 17 | ||||
-rw-r--r-- | app/views/action_text/content/_layout.html.erb | 3 | ||||
-rw-r--r-- | db/migrate/201805281641_create_action_text_tables.rb | 14 | ||||
-rw-r--r-- | lib/action_text/attribute.rb | 40 | ||||
-rw-r--r-- | lib/action_text/content.rb | 6 | ||||
-rw-r--r-- | lib/tasks/actiontext.rake | 28 | ||||
-rw-r--r-- | lib/templates/actiontext.css | 7 | ||||
-rw-r--r-- | package.json | 8 | ||||
-rw-r--r-- | test/dummy/app/views/messages/_form.html.erb | 2 | ||||
-rw-r--r-- | test/dummy/config/environments/development.rb | 2 | ||||
-rw-r--r-- | test/dummy/db/migrate/20180208205311_create_messages.rb | 2 | ||||
-rw-r--r-- | test/dummy/db/migrate/2018052816_create_action_text_tables.rb | 14 | ||||
-rw-r--r-- | test/dummy/db/schema.rb | 11 | ||||
-rw-r--r-- | test/unit/content_test.rb | 4 | ||||
-rw-r--r-- | test/unit/model_test.rb | 10 |
18 files changed, 173 insertions, 33 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index 116635c788..d40eabf684 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -71,7 +71,7 @@ GEM mini_mime (1.0.0) mini_portile2 (2.3.0) minitest (5.11.3) - nio4r (2.3.0) + nio4r (2.3.1) nokogiri (1.8.2) mini_portile2 (~> 2.3.0) rack (2.0.4) @@ -135,4 +135,4 @@ DEPENDENCIES webpacker (~> 3.2.2) BUNDLED WITH - 1.16.1 + 1.16.2 @@ -10,8 +10,8 @@ Assumes a Rails 5.2+ application with Active Storage and Webpacker installed. ```ruby # Gemfile - gem "activetext", github: "basecamp/activetext", require: "action_text" - gem "mini_magick" # for Active Storage variants + gem "actiontext", github: "basecamp/actiontext", require: "action_text" + gem "image_processing", "~> 1.2" # for Active Storage variants ``` 1. Install the npm package: @@ -19,7 +19,7 @@ Assumes a Rails 5.2+ application with Active Storage and Webpacker installed. ```js // package.json "dependencies": { - "activetext": "basecamp/activetext" + "actiontext": "basecamp/actiontext" } ``` @@ -29,9 +29,17 @@ Assumes a Rails 5.2+ application with Active Storage and Webpacker installed. ```js // app/javascript/packs/application.js - import "activetext" + import "actiontext" ``` +1. Migrate the database + + ``` + ./bin/rails active_storage:install + ./bin/rails action_text:install + ./bin/rails db:migrate + ``` + 1. Declare text columns as Action Text attributes: ```ruby @@ -41,7 +49,7 @@ Assumes a Rails 5.2+ application with Active Storage and Webpacker installed. end ``` -1. Replace form `text_area`s with `rich_text_field`s: +1. Replace form `text_area`s with `rich_text_area`s: ```erb <%# app/views/messages/_form.html.erb %> @@ -49,7 +57,7 @@ Assumes a Rails 5.2+ application with Active Storage and Webpacker installed. … <div class="field"> <%= form.label :content %> - <%= form.rich_text_field :content %> + <%= form.rich_text_area :content %> </div> … <% end %> diff --git a/app/helpers/action_text/tag_helper.rb b/app/helpers/action_text/tag_helper.rb index bd2727bca6..0033538773 100644 --- a/app/helpers/action_text/tag_helper.rb +++ b/app/helpers/action_text/tag_helper.rb @@ -2,10 +2,12 @@ module ActionText module TagHelper cattr_accessor(:id, instance_accessor: false) { 0 } - def rich_text_field_tag(name, value = nil, options = {}) + def rich_text_area_tag(name, value = nil, options = {}) options = options.symbolize_keys options[:input] ||= "trix_input_#{ActionText::TagHelper.id += 1}" + options[:class] ||= "trix-content" + options[:data] ||= {} options[:data][:direct_upload_url] = rails_direct_uploads_url options[:data][:blob_url_template] = rails_service_blob_url(":signed_id", ":filename") @@ -26,23 +28,23 @@ module ActionView::Helpers options = @options.stringify_keys add_default_name_and_id(options) options["input"] ||= dom_id(object, [options["id"], :trix_input].compact.join("_")) - @template_object.rich_text_field_tag(options.delete("name"), editable_value, options) + @template_object.rich_text_area_tag(options.delete("name"), editable_value, options) end def editable_value - value.try(:to_trix_html) + value.body.try(:to_trix_html) end end module FormHelper - def rich_text_field(object_name, method, options = {}) + def rich_text_area(object_name, method, options = {}) Tags::ActionText.new(object_name, method, self, options).render end end class FormBuilder - def rich_text_field(method, options = {}) - @template.rich_text_field(@object_name, method, objectify_options(options)) + def rich_text_area(method, options = {}) + @template.rich_text_area(@object_name, method, objectify_options(options)) end end end diff --git a/app/models/action_text/rich_text.rb b/app/models/action_text/rich_text.rb new file mode 100644 index 0000000000..6747def9a5 --- /dev/null +++ b/app/models/action_text/rich_text.rb @@ -0,0 +1,17 @@ +class ActionText::RichText < ActiveRecord::Base + self.table_name = "action_text_rich_texts" + + serialize :body, ActionText::Content + + belongs_to :record, polymorphic: true, touch: true + has_many_attached :embeds + + after_save do + self.embeds_blobs = body.attachments.map(&:attachable) + end + + + def to_s + body.to_s.html_safe + end +end diff --git a/app/views/action_text/content/_layout.html.erb b/app/views/action_text/content/_layout.html.erb new file mode 100644 index 0000000000..c0b86a189e --- /dev/null +++ b/app/views/action_text/content/_layout.html.erb @@ -0,0 +1,3 @@ +<div class="trix-content"> + <%=raw document %> +</div> diff --git a/db/migrate/201805281641_create_action_text_tables.rb b/db/migrate/201805281641_create_action_text_tables.rb new file mode 100644 index 0000000000..872987749a --- /dev/null +++ b/db/migrate/201805281641_create_action_text_tables.rb @@ -0,0 +1,14 @@ +class CreateActionTextTables < ActiveRecord::Migration[5.2] + def change + create_table :action_text_rich_texts do |t| + t.string :name, null: false + t.text :body, limit: 16777215, null: false + t.references :record, null: false, polymorphic: true, index: false + + t.datetime :created_at, null: false + t.datetime :updated_at, null: false + + t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true + end + end +end diff --git a/lib/action_text/attribute.rb b/lib/action_text/attribute.rb index 32dd1d0e3f..299c26b29f 100644 --- a/lib/action_text/attribute.rb +++ b/lib/action_text/attribute.rb @@ -3,15 +3,41 @@ module ActionText extend ActiveSupport::Concern class_methods do - def has_rich_text(attribute_name) - serialize(attribute_name, ActionText::Content) + # Provides access to a dependent RichText model that holds the body and attachments for a single named rich text attribute. + # This dependent attribute is lazily instantiated and will be auto-saved when it's been changed. Example: + # + # class Message < ActiveRecord::Base + # has_rich_text :content + # end + # + # message = Message.create!(content: "<h1>Funny times!</h1>") + # message.content.to_s # => "<h1>Funny times!</h1>" + # message.content.body.to_plain_text # => "Funny times!" + # + # The dependent RichText model will also automatically process attachments links as sent via the Trix-powered editor. + # These attachments are associated with the RichText model using Active Storage. + # + # If you wish to preload the dependent RichText model, you can use the named scope: + # + # Message.all.with_rich_text_content # Avoids N+1 queries when you just want the body, not the attachments. + # Message.all.with_rich_text_content_and_emebds # Avoids N+1 queries when you just want the body and attachments. + def has_rich_text(name) + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name} + self.rich_text_#{name} ||= ActionText::RichText.new(name: "#{name}", record: self) + end - has_many_attached "#{attribute_name}_attachments" + def #{name}=(body) + #{name}.body = body + end + CODE - after_save do - blobs = public_send(attribute_name).attachments.map(&:attachable) - public_send("#{attribute_name}_attachments_blobs=", blobs) - end + has_one :"rich_text_#{name}", -> { where(name: name) }, class_name: "ActionText::RichText", as: :record, inverse_of: :record, dependent: :destroy + + scope :"with_rich_text_#{name}", -> { includes("rich_text_#{name}") } + scope :"with_rich_text_#{name}_and_embeds", -> { includes("rich_text_#{name}": { embeds_attachments: :blob }) } + + after_save { public_send(name).save if public_send(name).changed? } end end end diff --git a/lib/action_text/content.rb b/lib/action_text/content.rb index c63e0c3525..c296896ac4 100644 --- a/lib/action_text/content.rb +++ b/lib/action_text/content.rb @@ -53,8 +53,12 @@ module ActionText end.to_html end + def to_html_with_layout + ActionText.renderer.render(partial: "action_text/content/layout", locals: { document: to_html }) + end + def to_s - to_html.html_safe + to_html_with_layout end def as_json(*) diff --git a/lib/tasks/actiontext.rake b/lib/tasks/actiontext.rake new file mode 100644 index 0000000000..817a4c67f0 --- /dev/null +++ b/lib/tasks/actiontext.rake @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +namespace :action_text do + # Prevent migration installation task from showing up twice. + Rake::Task["install:migrations"].clear_comments + + desc "Copy over the migration, stylesheet, and JavaScript files" + task install: %i( environment copy_migration copy_stylesheet ) + + task :copy_migration do + if Rake::Task.task_defined?("action_text:install:migrations") + Rake::Task["action_text:install:migrations"].invoke + else + Rake::Task["app:action_text:install:migrations"].invoke + end + end + + STYLESHEET_TEMPLATE_PATH = File.expand_path("../templates/actiontext.css", __dir__) + STYLESHEET_APP_PATH = Rails.root.join("app/assets/stylesheets/actiontext.css") + + task :copy_stylesheet do + if File.exist?(STYLESHEET_APP_PATH) + puts "Won't copy Action Text stylesheet as it already exists" + else + FileUtils.cp STYLESHEET_TEMPLATE_PATH, STYLESHEET_APP_PATH + end + end +end diff --git a/lib/templates/actiontext.css b/lib/templates/actiontext.css new file mode 100644 index 0000000000..97c083c24b --- /dev/null +++ b/lib/templates/actiontext.css @@ -0,0 +1,7 @@ +/* + * Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and + * the trix-editor content (whether displayed or under editing). Feel free to incorporate this + * inclusion directly in any other asset bundle and remove this file. + * + *= require trix/dist/trix +*/ diff --git a/package.json b/package.json index 0f11cee9b5..ef4481fbfd 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "activetext", + "name": "actiontext", "version": "0.1.0", "description": "Edit and display rich text in Rails applications", - "main": "app/javascript/activetext/index.js", + "main": "app/javascript/actiontext/index.js", "files": [ - "app/javascript/activetext/*.js" + "app/javascript/actiontext/*.js" ], - "repository": "https://github.com/basecamp/activetext", + "repository": "https://github.com/basecamp/actiontext", "author": "Basecamp, LLC", "contributors": [ "Javan Makhmali <javan@javan.us>", diff --git a/test/dummy/app/views/messages/_form.html.erb b/test/dummy/app/views/messages/_form.html.erb index bd5f0bea4a..3b8a174884 100644 --- a/test/dummy/app/views/messages/_form.html.erb +++ b/test/dummy/app/views/messages/_form.html.erb @@ -18,7 +18,7 @@ <div class="field"> <%= form.label :content %> - <%= form.rich_text_field :content, class: "trix-content" %> + <%= form.rich_text_area :content, class: "trix-content" %> </div> <div class="actions"> diff --git a/test/dummy/config/environments/development.rb b/test/dummy/config/environments/development.rb index 42768bf68c..f09b9a00c4 100644 --- a/test/dummy/config/environments/development.rb +++ b/test/dummy/config/environments/development.rb @@ -1,6 +1,6 @@ Rails.application.configure do # Verifies that versions and hashed value of the package contents in the project's package.json - config.webpacker.check_yarn_integrity = true + # config.webpacker.check_yarn_integrity = true # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on diff --git a/test/dummy/db/migrate/20180208205311_create_messages.rb b/test/dummy/db/migrate/20180208205311_create_messages.rb index 6817d5933f..8e43559672 100644 --- a/test/dummy/db/migrate/20180208205311_create_messages.rb +++ b/test/dummy/db/migrate/20180208205311_create_messages.rb @@ -2,8 +2,6 @@ class CreateMessages < ActiveRecord::Migration[5.2] def change create_table :messages do |t| t.string :subject - t.text :content - t.timestamps end end diff --git a/test/dummy/db/migrate/2018052816_create_action_text_tables.rb b/test/dummy/db/migrate/2018052816_create_action_text_tables.rb new file mode 100644 index 0000000000..872987749a --- /dev/null +++ b/test/dummy/db/migrate/2018052816_create_action_text_tables.rb @@ -0,0 +1,14 @@ +class CreateActionTextTables < ActiveRecord::Migration[5.2] + def change + create_table :action_text_rich_texts do |t| + t.string :name, null: false + t.text :body, limit: 16777215, null: false + t.references :record, null: false, polymorphic: true, index: false + + t.datetime :created_at, null: false + t.datetime :updated_at, null: false + + t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true + end + end +end diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index f892368183..30bf844e9c 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -12,6 +12,16 @@ ActiveRecord::Schema.define(version: 2018_02_12_164506) do + create_table "action_text_rich_texts", force: :cascade do |t| + t.string "name", null: false + t.text "body", limit: 16777215, null: false + t.string "record_type", null: false + t.integer "record_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true + end + create_table "active_storage_attachments", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false @@ -35,7 +45,6 @@ ActiveRecord::Schema.define(version: 2018_02_12_164506) do create_table "messages", force: :cascade do |t| t.string "subject" - t.text "content" t.datetime "created_at", null: false t.datetime "updated_at", null: false end diff --git a/test/unit/content_test.rb b/test/unit/content_test.rb index e5773361e4..b1a06e2cfa 100644 --- a/test/unit/content_test.rb +++ b/test/unit/content_test.rb @@ -3,8 +3,8 @@ require_relative '../test_helper' module ActionText class ContentTest < ActiveSupport::TestCase test "plain text conversion" do - message = Message.create!(subject: "Greetings", content: "<h1>Hello world</h1>") - assert_equal "Hello world", message.content.to_plain_text + message = Message.new(subject: "Greetings", content: "<h1>Hello world</h1>") + assert_equal "Hello world", message.content.body.to_plain_text end end end diff --git a/test/unit/model_test.rb b/test/unit/model_test.rb new file mode 100644 index 0000000000..b7c5333c4d --- /dev/null +++ b/test/unit/model_test.rb @@ -0,0 +1,10 @@ +require_relative '../test_helper' + +module ActionText + class ModelTest < ActiveSupport::TestCase + test "saving content" do + message = Message.create!(subject: "Greetings", content: "<h1>Hello world</h1>") + assert_equal "Hello world", message.content.body.to_plain_text + end + end +end |